pact-foundation / pact-foundation/pact-reference
Matchers: Numeric matcher passing test sending String value
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 106
- Forks
- 53
- Avg merge
- 1h 37m
- Merged PRs (30d)
- 2
Description
🌎 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:
- 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\”
}"
- Prepare an encodable type
Body(key1: String, key2: String)that takes strings for property’s values, - Send a POST request with the
Bodytype and string values, - 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:
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the false positive through the pactffi_with_body FFI path, then inspect the pact_ffi::mock_server::handles entry point and the pact_matching body-comparison output shown in the logs. Done means a JSON body sending string values for numeric matcher fields is rejected with a mismatch instead of being reported as matched.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100