pact-foundation / pact-reference

Reference implementations for the pact specifications
https://pact.io
MIT License
91 stars 46 forks source link

multi-valued headers are incorrectly verified #300

Closed mefellows closed 1 year ago

mefellows commented 1 year ago

Given the following test that specifies multiple accept header values:

    {
      // Define the Pact for the test, specify the names of the consuming
      // application and the provider application.
      let alice_service = PactBuilder::new_v4("Consumer", "Alice Service")
        // Start a new interaction. We can add as many interactions as we want.
        .interaction("a retrieve Mallory request", "", |mut i| {
          // Defines a provider state. It is optional.
          i.given("there is some good mallory");
          // Define the request, a GET (default) request to '/mallory'.
          i.request.path("/mallory");
          i.request.header("accept", "application/problem+json, application/json, text/plain, */*");
          // Define the response we want returned.
          i.response
            .ok()
            .content_type("text/plain")
            .body("That is some good Mallory.");

          // Return the interaction back to the pact framework
          i.clone()
        })
        .start_mock_server(None);

      // You would use your actual client code here.
      let mallory_url = alice_service.path("/mallory");
      let client = reqwest::Client::new();
      let response = client.get(mallory_url)
        .header("accept", "application/problem+json, application/json, text/plain, */*")
        .send().await
        .expect("could not fetch URL");
      let body = response.text().await.expect("could not read response body");
      assert_eq!(body, "That is some good Mallory.");
    }

it fails with:

mock server Consumer/Alice Service failed verification:
- request HTTP Request ( method: GET, path: /mallory, query: None, headers: Some({"accept": ["application/problem+json, application/json, text/plain, */*"], "content-type": ["application/json"]}), body: Missing ):
  - Mismatch with header 'accept': Expected header 'accept' to have value 'application/problem+json, application/json, text/plain, */*' but was 'application/problem+json'
  - Mismatch with header 'accept': Expected header 'accept' to have value '' but was 'application/json'
  - Mismatch with header 'accept': Expected header 'accept' to have value '' but was 'text/plain'
  - Mismatch with header 'accept': Expected header 'accept' to have value '' but was '*/*'

See repro: https://github.com/pact-foundation/pact-reference/compare/repro/pact-js-issue-1031?expand=1

You can run the test with cargo test mock_server_passing_validation -- --exact

Relates to https://github.com/pact-foundation/pact-js/issues/964 and https://github.com/pact-foundation/pact-js/issues/1061

FFI Related Issue

The FFI seems to behave slightly differently.

Given this JS example:

describe('get /dogs', () => {
    before(() => {
      const interaction = {
        state: 'i have a list of dogs',
        uponReceiving: 'a request for all dogs',
        withRequest: {
          method: 'GET',
          path: '/dogs',
          headers: {
            Accept:
              'application/problem+json, application/json, text/plain, */*', // <- fails ❌
              // Accept: [                                                      <- works ✅
              //   
              //   'application/problem+json',
              //   'application/json',
              //   'text/plain',
              //   '*/*',
              // ],
          },
        },
        willRespondWith: {
          status: 200,
          headers: {
            'Content-Type': 'application/json',
          },
          body: EXPECTED_BODY,
        },
      };
      return provider.addInteraction(interaction);
    });

    it('returns the correct response', async () => {
      return axios.request({
        method: 'GET',
        baseURL: `${url}:${port}`,
        url: '/dogs',
        headers: {
          Accept: [
            'application/problem+json',
            'application/json',
            'text/plain',
            '*/*',
          ],
        },
      });
    });
  });

The failure is:

Mismatch with header 'Accept': Expected header 'Accept' to have value 'application/problem+jsonapplication/jsontext/plain*/*' but was 'application/problem+json'

It seems to be concatenating all of comma separated values when a multi-valued header is defined as a string, but not when each header value is independently passed in. The values are being set via the pactffi_with_header_v2 method.

TRACE level log

I've confirmed the JS library is sending an unadulterated header string. There is an entry in the log I extracted:

[22:53:23.324] DEBUG (11742): pact@11.0.2: setting header request value for Accept at index 0 to "application/problem+json, application/json, text/plain, */*"

The rust logs are:

2023-07-08T12:53:23.302001Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 1, keys = [1]
2023-07-08T12:53:23.302085Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V3 } }
2023-07-08T12:53:23.302152Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.302271Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 1, keys = [1]
2023-07-08T12:53:23.302277Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.302301Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.302489Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 1, keys = [1]
2023-07-08T12:53:23.302494Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.302574Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.319285Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.319297Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.319301Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.319336Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.319380Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.319385Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.319562Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.324525Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.324561Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.326584Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.326592Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.326595Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.327710Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.327718Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.327721Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing, matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.327948Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.327960Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.327963Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing, matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
[22:53:23.328] DEBUG (11742): pact@11.0.2: setting header response value for application/json at index 0 to "application/json"
2023-07-08T12:53:23.328969Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - index = 1, interaction = 1
2023-07-08T12:53:23.328975Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - keys = [1]
2023-07-08T12:53:23.328978Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing, matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: Some({"Content-Type": ["application/json"]}), body: Present(b"[{\"dog\":1},{\"dog\":2}]", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), matching_rules: MatchingRules { rules: {BODY: MatchingRuleCategory { name: BODY, rules: {} }} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 }
2023-07-08T12:53:23.330384Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_ffi::mock_server::handles: with_pact - ref = 1, keys = [1]
2023-07-08T12:53:23.330401Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_ffi::mock_server::handles: with_pact before - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing, matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: Some({"Content-Type": ["application/json"]}), body: Present(b"[{\"dog\":1},{\"dog\":2}]", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), matching_rules: MatchingRules { rules: {BODY: MatchingRuleCategory { name: BODY, rules: {} }, HEADER: MatchingRuleCategory { name: HEADER, rules: {} }} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.330546Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_plugin_driver::catalogue_manager: register_core_entries([CatalogueEntry { entry_type: TRANSPORT, provider_type: CORE, plugin: None, key: "http", values: {} }, CatalogueEntry { entry_type: TRANSPORT, provider_type: CORE, plugin: None, key: "https", values: {} }])
2023-07-08T12:53:23.330620Z DEBUG ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_plugin_driver::catalogue_manager: Updated catalogue entries:
core/transport/http
core/transport/https
2023-07-08T12:53:23.330651Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_plugin_driver::catalogue_manager: register_core_entries([CatalogueEntry { entry_type: CONTENT_MATCHER, provider_type: CORE, plugin: None, key: "xml", values: {"content-types": "application/.*xml,text/xml"} }, CatalogueEntry { entry_type: CONTENT_MATCHER, provider_type: CORE, plugin: None, key: "json", values: {"content-types": "application/.*json,application/json-rpc,application/jsonrequest"} }, CatalogueEntry { entry_type: CONTENT_MATCHER, provider_type: CORE, plugin: None, key: "text", values: {"content-types": "text/plain"} }, CatalogueEntry { entry_type: CONTENT_MATCHER, provider_type: CORE, plugin: None, key: "multipart-form-data", values: {"content-types": "multipart/form-data,multipart/mixed"} }, CatalogueEntry { entry_type: CONTENT_GENERATOR, provider_type: CORE, plugin: None, key: "json", values: {"content-types": "application/.*json,application/json-rpc,application/jsonrequest"} }, CatalogueEntry { entry_type: CONTENT_GENERATOR, provider_type: CORE, plugin: None, key: "binary", values: {"content-types": "application/octet-stream"} }])
2023-07-08T12:53:23.330695Z DEBUG ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: 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-08T12:53:23.330709Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_plugin_driver::catalogue_manager: register_core_entries([CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v2-regex", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v2-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-number-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-integer-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-decimal-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-date", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-time", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-datetime", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v2-min-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v2-max-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v2-minmax-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-includes", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-null", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-equals-ignore-order", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-min-equals-ignore-order", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-max-equals-ignore-order", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-minmax-equals-ignore-order", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v3-content-type", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-array-contains", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v1-equality", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-not-empty", values: {} }, CatalogueEntry { entry_type: MATCHER, provider_type: CORE, plugin: None, key: "v4-semver", values: {} }])
2023-07-08T12:53:23.330830Z DEBUG ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: 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
2023-07-08T12:53:23.331124Z DEBUG ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_mock_server::mock_server: Started mock server on 127.0.0.1:8992
2023-07-08T12:53:23.331173Z TRACE ThreadId(01) pactffi_create_mock_server_for_pact{pact=PactHandle { pact_ref: 1 } addr_str=0x30e843f59 tls=false}: pact_ffi::mock_server::handles: with_pact after - ref = 1, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for all dogs", provider_states: [ProviderState { name: "i have a list of dogs", params: {} }], request: HttpRequest { method: "GET", path: "/dogs", query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing, matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: Some({"Content-Type": ["application/json"]}), body: Present(b"[{\"dog\":1},{\"dog\":2}]", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), matching_rules: MatchingRules { rules: {BODY: MatchingRuleCategory { name: BODY, rules: {} }, HEADER: MatchingRuleCategory { name: HEADER, rules: {} }} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: true, specification_version: V2 } }
2023-07-08T12:53:23.347917Z TRACE tokio-runtime-worker hyper::proto::h1::conn: Conn::read_head
2023-07-08T12:53:23.347945Z TRACE tokio-runtime-worker hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Busy }
2023-07-08T12:53:23.348856Z TRACE tokio-runtime-worker hyper::proto::h1::conn: Conn::read_head
2023-07-08T12:53:23.348873Z TRACE tokio-runtime-worker hyper::proto::h1::io: received 182 bytes
2023-07-08T12:53:23.348889Z TRACE tokio-runtime-worker parse_headers: hyper::proto::h1::role: Request.parse bytes=182
2023-07-08T12:53:23.348908Z TRACE tokio-runtime-worker parse_headers: hyper::proto::h1::role: Request.parse Complete(182)
2023-07-08T12:53:23.348987Z DEBUG tokio-runtime-worker hyper::proto::h1::io: parsed 7 headers
2023-07-08T12:53:23.348997Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body is empty
2023-07-08T12:53:23.349000Z TRACE tokio-runtime-worker hyper::proto::h1::conn: remote disabling keep-alive
2023-07-08T12:53:23.349047Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request
2023-07-08T12:53:23.349057Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Extracting query from uri /dogs
2023-07-08T12:53:23.349063Z TRACE tokio-runtime-worker pact_mock_server::hyper_server: path_and_query -> /dogs
2023-07-08T12:53:23.349127Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request HTTP Request ( method: GET, path: /dogs, query: None, headers: Some({"user-agent": ["axios/0.27.2"], "connection": ["close"], "host": ["127.0.0.1:8992"], "accept": ["application/problem+json", "application/json", "text/plain", "*/*"]}), body: Empty )
2023-07-08T12:53:23.349223Z  INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: GET, path: /dogs, query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing )
2023-07-08T12:53:23.349236Z DEBUG tokio-runtime-worker pact_matching:      body: ''
2023-07-08T12:53:23.349239Z DEBUG tokio-runtime-worker pact_matching:      matching_rules: MatchingRules { rules: {HEADER: MatchingRuleCategory { name: HEADER, rules: {} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }} }
2023-07-08T12:53:23.349244Z DEBUG tokio-runtime-worker pact_matching:      generators: Generators { categories: {} }
2023-07-08T12:53:23.349254Z TRACE tokio-runtime-worker pact_matching: plugin_data = {}
2023-07-08T12:53:23.349297Z TRACE tokio-runtime-worker matcher_is_defined{self=CoreMatchingContext { matchers: MatchingRuleCategory { name: PATH, rules: {} }, config: NoUnexpectedKeys, matching_spec: V3, plugin_configuration: {} } path=DocPath { path_tokens: [], expr: "" }}: pact_models::matchingrules: matcher_is_defined: for category path and path [] -> false
2023-07-08T12:53:23.351116Z DEBUG tokio-runtime-worker matches_with{self="/dogs" actual="/dogs" matcher=Equality cascaded=false}: pact_matching::matchers: String -> String: comparing '/dogs' to '/dogs' ==> true cascaded=false matcher=Equality
2023-07-08T12:53:23.351138Z DEBUG tokio-runtime-worker pact_matching: expected content type = '*/*', actual content type = '*/*'
2023-07-08T12:53:23.351184Z DEBUG tokio-runtime-worker pact_matching: content type header matcher = 'RuleList { rules: [], rule_logic: And, cascaded: false }'
2023-07-08T12:53:23.351241Z TRACE tokio-runtime-worker match_header_value{key="Accept" index=0 expected="application/problem+json, application/json, text/plain, */*" actual="application/problem+json" context=CoreMatchingContext { matchers: MatchingRuleCategory { name: HEADER, rules: {} }, config: NoUnexpectedKeys, matching_spec: V3, plugin_configuration: {} } single_value=false}:matcher_is_defined{self=CoreMatchingContext { matchers: MatchingRuleCategory { name: HEADER, rules: {} }, config: NoUnexpectedKeys, matching_spec: V3, plugin_configuration: {} } path=DocPath { path_tokens: [Root, Field("Accept")], expr: "$.Accept" }}: pact_models::matchingrules: matcher_is_defined: for category header and path ["$", "Accept"] -> false
2023-07-08T12:53:23.351256Z TRACE tokio-runtime-worker match_header_value{key="Accept" index=0 expected="application/problem+json, application/json, text/plain, */*" actual="application/problem+json" context=CoreMatchingContext { matchers: MatchingRuleCategory { name: HEADER, rules: {} }, config: NoUnexpectedKeys, matching_spec: V3, plugin_configuration: {} } single_value=false}:matcher_is_defined{self=CoreMatchingContext { matchers: MatchingRuleCategory { name: HEADER, rules: {} }, config: NoUnexpectedKeys, matching_spec: V3, plugin_configuration: {} } path=DocPath { path_tokens: [Root, Field("Accept"), Index(0)], expr: "$.Accept[0]" }}: pact_models::matchingrules: matcher_is_defined: for category header and path ["$", "Accept", "0"] -> false
2023-07-08T12:53:23.351666Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: [HeaderMismatch { key: "Accept", expected: "application/problem+jsonapplication/jsontext/plain*/*", actual: "application/problem+json", mismatch: "Mismatch with header 'Accept': Expected header 'Accept' to have value 'application/problem+jsonapplication/jsontext/plain*/*' but was 'application/problem+json'" }]
2023-07-08T12:53:23.351744Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Request did not match: Request did not match - HTTP Request ( method: GET, path: /dogs, query: None, headers: Some({"Accept": ["application/problem+json, application/json, text/plain, */*"]}), body: Missing )    0) Mismatch with header 'Accept': Expected header 'Accept' to have value 'application/problem+jsonapplication/jsontext/plain*/*' but was 'application/problem+json'
2023-07-08T12:53:23.351819Z TRACE tokio-runtime-worker encode_headers: hyper::proto::h1::role: Server::encode status=500, body=Some(Known(398)), req_method=Some(GET)
2023-07-08T12:53:23.351849Z TRACE tokio-runtime-worker hyper::proto::h1::io: buffer.queue self.len=201 buf.len=398
2023-07-08T12:53:23.351879Z DEBUG tokio-runtime-worker hyper::proto::h1::io: flushed 599 bytes
2023-07-08T12:53:23.351886Z TRACE tokio-runtime-worker hyper::proto::h1::conn: State::close()
2023-07-08T12:53:23.351888Z TRACE tokio-runtime-worker hyper::proto::h1::conn: flushed({role=server}): State { reading: Closed, writing: Closed, keep_alive: Disabled }
2023-07-08T12:53:23.351906Z TRACE tokio-runtime-worker hyper::proto::h1::conn: shut down IO complete
      1) returns the correct response

Pact verification failed!
Test failed for the following reasons:

  Mock server failed with the following mismatches:

    0) The following request was incorrect:

                GET /dogs

             1.0 Mismatch with header 'Accept': Expected header 'Accept' to have value 'application/problem+jsonapplication/jsontext/plain*/*' but was 'application/problem+json'
[22:53:23.357] TRACE (11742): pact-core@13.15.0: Ffi has already been initialised, no need to repeat it
2023-07-08T12:53:23.360262Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 2, keys = [1, 2]
2023-07-08T12:53:23.360276Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V3 } }
2023-07-08T12:53:23.360285Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.360297Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 2, keys = [1, 2]
2023-07-08T12:53:23.360352Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.360366Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.362710Z 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-08T12:53:23.362727Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID b736dacc-4893-460f-8ea5-ac8db55fdb42 - MockServerMetrics { requests: 1 }
2023-07-08T12:53:23.362738Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server b736dacc-4893-460f-8ea5-ac8db55fdb42 shutdown - MockServerMetrics { requests: 1 }
2023-07-08T12:53:23.362758Z DEBUG tokio-runtime-worker hyper::server::shutdown: signal received, starting graceful shutdown
2023-07-08T12:53:23.362834Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact - ref = 2, keys = [1, 2]
2023-07-08T12:53:23.362840Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact before - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
2023-07-08T12:53:23.362872Z TRACE ThreadId(01) pact_ffi::mock_server::handles: with_pact after - ref = 2, inner = RefCell { value: PactHandleInner { pact: V4Pact { consumer: Consumer { name: "MyConsumer" }, provider: Provider { name: "MyProvider" }, interactions: [SynchronousHttp { id: None, key: None, description: "", provider_states: [], request: HttpRequest { method: "GET", path: "/", query: None, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, response: HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } }, comments: {}, pending: false, plugin_config: {}, interaction_markup: InteractionMarkup { markup: "", markup_type: "" }, transport: None }], metadata: {"pact-js": Object {"version": String("11.0.2")}, "pactRust": Object {"ffi": String("0.4.0")}}, plugin_data: [] }, mock_server_started: false, specification_version: V2 } }
github-actions[bot] commented 1 year ago

👋 Hi! The 'smartbear-supported' label has just been added to this issue, which will create an internal tracking ticket in PactFlow's Jira (PACT-1173). We will use this to prioritise and assign a team member to this task. All activity will be public on this ticket. For now, sit tight and we'll update this ticket once we have more information on the next steps.

See our documentation for more information.

mefellows commented 1 year ago

I believe this is fixed now, closing.