pact-foundation / pact-reference

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

Multipart: last part should not override all previous parts #314

Closed tienvx closed 10 months ago

tienvx commented 10 months ago

Call pactffi_with_multipart_file with multi parts:

All calls are success (no errors return). But only the last part (personal_note) are recorded. Other parts are missing from the pact:

Pact file ```json { "consumer": { "name": "multipartConsumer" }, "interactions": [ { "description": "A put request to /user-profile", "providerStates": [ { "name": "User exists" } ], "request": { "body": "--MxafNCX4RAVZ1d2c\r\nContent-Disposition: form-data; name=\"personal_note\"; filename=\"pactmk99lD\"\r\nContent-Type: application/octet-stream\r\n\r\ntesting\r\n--MxafNCX4RAVZ1d2c--\r\n", "headers": { "Accept": "application/json", "Authorization": "Bearer eyJhbGciOiJIUzI1NiIXVCJ9", "Content-Type": "multipart/form-data; boundary=MxafNCX4RAVZ1d2c" }, "matchingRules": { "body": { "$.full_name": { "combine": "AND", "matchers": [ { "match": "contentType", "value": "text/plain" } ] }, "$.personal_note": { "combine": "AND", "matchers": [ { "match": "contentType", "value": "text/plain" } ] }, "$.profile_image": { "combine": "AND", "matchers": [ { "match": "contentType", "value": "image/jpeg" } ] } }, "header": { "$.Authorization[0]": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "Content-Type": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*" }, { "match": "regex", "regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*" }, { "match": "regex", "regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*" } ] } } }, "method": "POST", "path": "/user-profile" }, "response": { "body": { "full_name": "Colten Ziemann", "personal_note": "testing", "profile_image": "http://example.test/profile-image.jpg" }, "headers": { "Content-Type": "application/json" }, "matchingRules": { "body": { "$.full_name": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.personal_note": { "combine": "AND", "matchers": [ { "match": "type" } ] }, "$.profile_image": { "combine": "AND", "matchers": [ { "match": "regex", "regex": "^https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()!@:%_\\+.~#?&\\/\\/=]*)" } ] } }, "header": {} }, "status": 200 } } ], "metadata": { "pactRust": { "ffi": "0.4.7", "mockserver": "1.2.3", "models": "1.1.9" }, "pactSpecification": { "version": "3.0.0" } }, "provider": { "name": "multipartProvider" } } ```
Log ``` 2023-08-21T12:21:30.542434Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: parsed header value: Left("application/json") 2023-08-21T12:21:30.542470Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: detected pact:matcher:type, will configure a matcher 2023-08-21T12:21:30.542490Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: parsed header value: Left("Bearer eyJhbGciOiJIUzI1NiIXVCJ9") 2023-08-21T12:21:30.542587Z DEBUG ThreadId(01) pact_ffi::mock_server::handles: parsed header value: Left("application/json") 2023-08-21T12:21:30.542632Z DEBUG ThreadId(01) pact_ffi::mock_server: pact_ffi::mock_server::pactffi_create_mock_server_for_transport FFI function invoked 2023-08-21T12:21:30.542749Z DEBUG ThreadId(01) pact_plugin_driver::catalogue_manager: Updated catalogue entries: core/transport/http core/transport/https 2023-08-21T12:21:30.542774Z 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-08-21T12:21:30.542804Z 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 2023-08-21T12:21:30.542934Z DEBUG ThreadId(01) pact_mock_server::mock_server: Started mock server on 127.0.0.1:38797 2023-08-21T12:21:30.547431Z DEBUG tokio-runtime-worker hyper::proto::h1::io: parsed 6 headers 2023-08-21T12:21:30.547438Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body is content-length (790 bytes) 2023-08-21T12:21:30.547452Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body completed 2023-08-21T12:21:30.547460Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request 2023-08-21T12:21:30.547464Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Extracting query from uri /user-profile 2023-08-21T12:21:30.547481Z INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request POST /user-profile 2023-08-21T12:21:30.547486Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: ---------------------------------------------------------------------------------------- method: POST path: /user-profile query: None headers: Some({"content-type": ["multipart/form-data; boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88"], "user-agent": ["GuzzleHttp/7"], "content-length": ["790"], "accept": ["application/json"], "authorization": ["Bearer ZmluLWFwaTphcGktc2VjcmV0"], "host": ["localhost:38797"]}) body: Present(790 bytes, multipart/form-data;boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88) ---------------------------------------------------------------------------------------- 2023-08-21T12:21:30.547529Z INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: POST, path: /user-profile, query: None, headers: Some({"Content-Type": ["multipart/form-data; boundary=Lc4PNfJQIDD2TWzb"], "Accept": ["application/json"], "Authorization": ["Bearer eyJhbGciOiJIUzI1NiIXVCJ9"]}), body: Present(170 bytes, multipart/form-data) ) 2023-08-21T12:21:30.547537Z DEBUG tokio-runtime-worker pact_matching: body: '2D2D4C6334504E664A514944443254577A62DA436F6E74656E742D44697370... (170 bytes)' 2023-08-21T12:21:30.547540Z 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: [Type], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root, Field("Content-Type")], expr: "Content-Type" }: RuleList { rules: [Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*"), Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*"), Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*")], rule_logic: And, cascaded: false }} }, BODY: MatchingRuleCategory { name: BODY, rules: {DocPath { path_tokens: [Root, Field("personal_note")], expr: "$.personal_note" }: RuleList { rules: [ContentType("text/plain")], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root, Field("full_name")], expr: "$.full_name" }: RuleList { rules: [ContentType("text/plain")], rule_logic: And, cascaded: false }, DocPath { path_tokens: [Root, Field("profile_image")], expr: "$.profile_image" }: RuleList { rules: [ContentType("image/jpeg")], rule_logic: And, cascaded: false }} }} } 2023-08-21T12:21:30.547555Z DEBUG tokio-runtime-worker pact_matching: generators: Generators { categories: {} } 2023-08-21T12:21:30.547577Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing '/user-profile' to '/user-profile' ==> true cascaded=false matcher=Equality 2023-08-21T12:21:30.547585Z DEBUG tokio-runtime-worker pact_matching: expected content type = 'multipart/form-data', actual content type = 'multipart/form-data;boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88' 2023-08-21T12:21:30.547600Z DEBUG tokio-runtime-worker pact_matching: content type header matcher = 'RuleList { rules: [], rule_logic: And, cascaded: false }' 2023-08-21T12:21:30.547604Z DEBUG tokio-runtime-worker pact_plugin_driver::catalogue_manager: Looking for a content matcher for multipart/form-data 2023-08-21T12:21:30.547907Z DEBUG tokio-runtime-worker pact_matching: No content matcher defined for content type 'multipart/form-data', using core matcher implementation 2023-08-21T12:21:30.547912Z DEBUG tokio-runtime-worker pact_matching: Using body matcher for content type 'multipart/form-data' 2023-08-21T12:21:30.547975Z DEBUG ThreadId(03) pact_matching::binary_utils: Could not get the tokio runtime, will try start a new one: there is no reactor running, must be called from the context of a Tokio 1.x runtime 2023-08-21T12:21:30.548541Z DEBUG ThreadId(03) pact_matching::binary_utils: matching MIME multipart contents 2023-08-21T12:21:30.548582Z DEBUG ThreadId(03) pact_matching::binary_utils: Expected has 1 part(s), actual has 3 part(s) 2023-08-21T12:21:30.548586Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing MIME multipart 0:'personal_note' 2023-08-21T12:21:30.548588Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing MIME part 'personal_note' as binary data 2023-08-21T12:21:30.548600Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing '"form-data; name=\"personal_note\"; filename=\"pactkhDBhK\""' to '"form-data; name=\"personal_note\"; filename=\"note.txt\""' at path '$.personal_note['content-disposition']' -> Ok(()) 2023-08-21T12:21:30.548606Z DEBUG ThreadId(03) pact_matching::matchers: String -> String: comparing 'application/octet-stream' to 'application/octet-stream' ==> true cascaded=false matcher=Equality 2023-08-21T12:21:30.548608Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing '"application/octet-stream"' to '"application/octet-stream"' at path '$.personal_note['content-type']' -> Ok(()) 2023-08-21T12:21:30.548611Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing headers at path '$.personal_note' -> Ok(()) 2023-08-21T12:21:30.548613Z DEBUG ThreadId(03) pact_matching::binary_utils: Expected part headers: {"content-disposition": "form-data; name=\"personal_note\"; filename=\"pactkhDBhK\"", "content-type": "application/octet-stream"} 2023-08-21T12:21:30.548616Z DEBUG ThreadId(03) pact_matching::binary_utils: Expected part body: [b"testing"] 2023-08-21T12:21:30.548618Z DEBUG ThreadId(03) pact_matching::binary_utils: Actual part headers: {"x-foo": "this is a note", "content-type": "application/octet-stream", "content-disposition": "form-data; name=\"personal_note\"; filename=\"note.txt\"", "content-length": "7"} 2023-08-21T12:21:30.548621Z DEBUG ThreadId(03) pact_matching::binary_utils: Actual part body: [b"testing"] 2023-08-21T12:21:30.548623Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing mime part 'personal_note': application/octet-stream -> application/octet-stream 2023-08-21T12:21:30.548630Z DEBUG ThreadId(03) pact_plugin_driver::catalogue_manager: Looking for a content matcher for application/octet-stream 2023-08-21T12:21:30.548885Z DEBUG ThreadId(03) pact_matching: No content matcher defined for content type 'application/octet-stream', using core matcher implementation 2023-08-21T12:21:30.548890Z DEBUG ThreadId(03) pact_matching: Using body matcher for content type 'application/octet-stream' 2023-08-21T12:21:30.548892Z DEBUG ThreadId(03) pact_matching::binary_utils: matching binary contents (7 bytes) 2023-08-21T12:21:30.548896Z DEBUG ThreadId(03) pact_matching::matchers: Bytes -> Bytes: comparing 7 bytes to 7 bytes using ContentType("text/plain") 2023-08-21T12:21:30.549327Z DEBUG ThreadId(03) pact_matching::binary_utils: Matching binary contents by content type: expected 'text/plain', detected 'text/plain' -> true 2023-08-21T12:21:30.549332Z DEBUG ThreadId(03) pact_matching::binary_utils: Comparing file part 'MimeFile { index: 0, name: "personal_note", content_type: Some("application/octet-stream"), filename: "pactkhDBhK", data: b"testing", headers: {"content-disposition": "form-data; name=\"personal_note\"; filename=\"pactkhDBhK\"", "content-type": "application/octet-stream"} }' to 'MimeFile { index: 2, name: "personal_note", content_type: Some("application/octet-stream"), filename: "note.txt", data: b"testing", headers: {"x-foo": "this is a note", "content-type": "application/octet-stream", "content-disposition": "form-data; name=\"personal_note\"; filename=\"note.txt\"", "content-length": "7"} }' at path '$.personal_note' -> Ok 2023-08-21T12:21:30.549396Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'multipart/form-data; boundary=Lc4PNfJQIDD2TWzb' to 'multipart/form-data; boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88' ==> true cascaded=false matcher=Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*") 2023-08-21T12:21:30.549409Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'multipart/form-data; boundary=Lc4PNfJQIDD2TWzb' to 'multipart/form-data; boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88' ==> true cascaded=false matcher=Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*") 2023-08-21T12:21:30.549418Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'multipart/form-data; boundary=Lc4PNfJQIDD2TWzb' to 'multipart/form-data; boundary=c98f23755f427af7661eb26adfa1dc5b85bb1e88' ==> true cascaded=false matcher=Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*") 2023-08-21T12:21:30.549431Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'Bearer eyJhbGciOiJIUzI1NiIXVCJ9' to 'Bearer ZmluLWFwaTphcGktc2VjcmV0' ==> true cascaded=false matcher=Type 2023-08-21T12:21:30.549436Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: [] 2023-08-21T12:21:30.549459Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Test context = {"mockServer": Object {"port": Number(38797), "url": String("http://127.0.0.1:38797")}} 2023-08-21T12:21:30.549468Z INFO tokio-runtime-worker pact_mock_server::hyper_server: Request matched, sending response 2023-08-21T12:21:30.549471Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: ---------------------------------------------------------------------------------------- status: 200 headers: Some({"Content-Type": ["application/json"]}) body: Present(112 bytes, application/json) '{"full_name":"Colten Ziemann","personal_note":"testing","profile_image":"http://example.test/profile-image.jpg"}' ---------------------------------------------------------------------------------------- 2023-08-21T12:21:30.549502Z DEBUG tokio-runtime-worker hyper::proto::h1::io: flushed 465 bytes 2023-08-21T12:21:30.550563Z INFO ThreadId(01) pact_mock_server::mock_server: Writing pact out to '/home/tien/Projects/pact/pact-php/example/multipart/consumer/tests/Service/../../../pacts/multipartConsumer-multipartProvider.json' 2023-08-21T12:21:30.550578Z DEBUG ThreadId(01) pact_models::pact: Merging pact with file "/home/tien/Projects/pact/pact-php/example/multipart/consumer/tests/Service/../../../pacts/multipartConsumer-multipartProvider.json" 2023-08-21T12:21:30.550653Z WARN ThreadId(01) pact_models::pact: Note: Existing pact is an older specification version (V3), and will be upgraded 2023-08-21T12:21:30.550792Z 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-08-21T12:21:30.550798Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID 90e61df1-4a16-4442-858a-4b9f95ec43ea - MockServerMetrics { requests: 1, requests_by_path: {"/user-profile": 1} } 2023-08-21T12:21:30.550804Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server 90e61df1-4a16-4442-858a-4b9f95ec43ea shutdown - MockServerMetrics { requests: 1, requests_by_path: {"/user-profile": 1} } 2023-08-21T12:21:30.550809Z DEBUG tokio-runtime-worker hyper::server::shutdown: signal received, starting graceful shutdown ```
rholshausen commented 10 months ago

This should be fixed with Pact FFI Library 0.4.8

tienvx commented 10 months ago

Thank @rholshausen . It's now working for me