Skip to content

Matchers: Numeric matcher passing test sending String value #484

Open
@surpher

Description

@surpher

🌎 Environment

  • Platform: pact_ffi
  • Version/Release: 0.4.25
  • Pact spec: v3, v4
  • FFI used:
bool pactffi_with_body(
   InteractionHandle interaction,
   enum InteractionPart part,
   const char *content_type,
   const char *body
);

💬 Description

Setting up unit tests for PactSwift verifying matchers in request body, specifically Numeric matcher. When executing the API request and Pact verification, verification succeeds with false positive:

2025-01-28T03:42:33.886570Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Request matched, sending response
2025-01-28T03:42:33.886572Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: 
          ----------------------------------------------------------------------------------------
           status: 200
           headers: None
           body: Missing
          ----------------------------------------------------------------------------------------
          
2025-01-28T03:42:33.886586Z TRACE tokio-runtime-worker encode_headers: hyper::proto::h1::role: Server::encode status=200, body=None, req_method=Some(POST)

🦶 Reproduction Steps

Steps to reproduce the behaviour:

  1. Setup a pact interaction that expects request body with Numeric value, ie
"{\"value\”:{
   \"key2\":{\"value\":321,\"pact:matcher:type\":\"number\”},
   \"key1\”:{\"pact:matcher:type\":\"number\",\"value\":123.1}},
   \"pact:matcher:type\":\"type\”
}"
  1. Prepare an encodable type Body(key1: String, key2: String) that takes strings for property’s values,
  2. Send a POST request with the Body type and string values,
  3. Verify pact interaction,

🤔 Expected Results

Pact test should fail due to invalid type being passed for the body keys’ values.

😲 Actual Results

Pact test succeeds with Request matched, sending response

----------------------------------------------------------------------------------------
           status: 200
           headers: None
           body: Missing
----------------------------------------------------------------------------------------

🌳 Logs

...
2025-01-28T04:09:13.515623Z TRACE ThreadId(02) pact_ffi::mock_server::handles: with_interaction - inner = PactHandleInner { pact: V4Pact { consumer: Consumer { name: "Consumer" }, provider: Provider { name: "Provider" }, interactions: [SynchronousHttp { id: None, key: None, description: "a request for a known path with a body matching number", provider_states: [], request: HttpRequest { method: "POST", path: "/jsonbody", query: None, headers: Some({"Content-Type": ["application/json"]}), body: Present(b"{\"key1\":123.1,\"key2\":321}", Some(ContentType { main_type: "application", sub_type: "json", attributes: {}, suffix: None }), None), matching_rules: MatchingRules { rules: {PATH: MatchingRuleCategory { name: PATH, rules: {} }, BODY: MatchingRuleCategory { name: BODY, rules: {DocPath { path_tokens: [Root, Field("key2")], expr: "$.key2" }: RuleList { rules: [Number], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root, Field("key1")], expr: "$.key1" }: RuleList { rules: [Number], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root], expr: "$" }: RuleList { rules: [Type], rule_logic: And, cascaded: false }} }} }, 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: {"namespace1": Object {"name1": String("value1")}, "namespace2": Object {"name2": String("value2")}, "pactRust": Object {"ffi": String("0.4.25")}}, plugin_data: [] }, mock_server_started: false, specification_version: V4 }

…

2025-01-28T04:09:54.935771Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request POST /jsonbody
2025-01-28T04:09:54.935775Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: 
      ----------------------------------------------------------------------------------------
       method: POST
       path: /jsonbody
       query: None
       headers: Some({"content-length": ["29"], "accept-encoding": ["gzip", "deflate"], "accept": ["*/*"], "accept-language": ["en-AU", "en;q=0.9"], "user-agent": ["xctest/23600 CFNetwork/1568.300.101 Darwin/24.2.0"], "host": ["127.0.0.1:4516"], "content-type": ["application/json"], "connection": ["keep-alive"]})
       body: Present(29 bytes, application/json) '{"key2":"456","key1":"321.1"}'
      ----------------------------------------------------------------------------------------

…

2025-01-28T04:09:54.935896Z DEBUG tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: POST, path: /jsonbody, query: None, headers: Some({"Content-Type": ["application/json"]}), body: Present(25 bytes, application/json) )
2025-01-28T04:09:54.935904Z DEBUG tokio-runtime-worker pact_matching:      body: '{"key1":123.1,"key2":321}'
2025-01-28T04:09:54.935907Z DEBUG tokio-runtime-worker pact_matching:      matching_rules: MatchingRules { rules: {PATH: MatchingRuleCategory { name: PATH, rules: {} }, BODY: MatchingRuleCategory { name: BODY, rules: {DocPath { path_tokens: [Root, Field("key2")], expr: "$.key2" }: RuleList { rules: [Number], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root, Field("key1")], expr: "$.key1" }: RuleList { rules: [Number], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root], expr: "$" }: RuleList { rules: [Type], rule_logic: And, cascaded: false }} }} }

…

2025-01-28T04:09:54.962229Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: []
2025-01-28T04:09:54.962254Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Test context = {"mockServer": Object {"port": Number(4516), "url": String("http://127.0.0.1:4516")}}
2025-01-28T04:09:54.962257Z TRACE tokio-runtime-worker pact_matching: generate_response response=HttpResponse { status: 200, headers: None, body: Missing, matching_rules: MatchingRules { rules: {} }, generators: Generators { categories: {} } } mode=Consumer context={"mockServer": Object {"port": Number(4516), "url": String("http://127.0.0.1:4516")}}
2025-01-28T04:09:54.962262Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Request matched, sending response
2025-01-28T04:09:54.962521Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: 
          ----------------------------------------------------------------------------------------
           status: 200
           headers: None
           body: Missing
          ----------------------------------------------------------------------------------------
          
2025-01-28T04:09:54.962539Z TRACE tokio-runtime-worker encode_headers: hyper::proto::h1::role: Server::encode status=200, body=None, req_method=Some(POST)
2025-01-28T04:09:54.962570Z DEBUG tokio-runtime-worker hyper::proto::h1::io: flushed 319 bytes

….

📄 Stack Traces

See attached false-positive-numeric-body-value for full standardOut: .trace log:

false-positive-numeric-body-value.log

🤝 Relationships

  • Related PRs or Issues:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugIndicates an unexpected problem or unintended behavior

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions