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

.withRequestMultipartFileUpload() detects application/pdf or application/octet-stream type as text/plain #1094

Open
5 tasks done
spawluk-zartis opened this issue May 18, 2023 · 4 comments
Labels
bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer

Comments

@spawluk-zartis
Copy link

Software versions

  • OS: Win 10, Ubuntu 22.04.2
  • Consumer Pact library: Pact JS v11.0.2
  • Provider Pact library: irrelevant
  • Node Version: v18.14.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

When testing multipart/form-data upload, PDF file should be detected as application/pdf or application/octet-stream

Actual behaviour

The uploaded PDF file is detected as text/plain.

Moreover on Azure DevOps agent running Ubuntu, the same file is detected as text/x-tex

I'm not 100% sure but I think it is related to mime type detection logic here

Steps to reproduce

Clone https://github.com/spawluk-zartis/pact-multipart-form-data-upload

npm install
npm test

Relevant log files

> pact-multipart-form-data-upload@1.0.0 test
> jest


 RUNS  tests/UploadPdf.test.ts
2023-05-18T08:16:51.469779Z DEBUG ThreadId(01) pact_plugin_driver::catalogue_manager: Updated catalogue entries:
core/transport/http
core/transport/https
2023-05-18T08:16:51.470707Z 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-05-18T08:16:51.472088Z 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
(node:29032) ExperimentalWarning: buffer.File is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

 RUNS  tests/UploadPdf.test.ts
2023-05-18T08:16:51.642547Z DEBUG tokio-runtime-worker hyper::proto::h1::io: parsed 7 headers
2023-05-18T08:16:51.642977Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body is content-length (981 bytes)
2023-05-18T08:16:51.643376Z DEBUG tokio-runtime-worker hyper::proto::h1::conn: incoming body completed
2023-05-18T08:16:51.643730Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Creating pact request from hyper request
2023-05-18T08:16:51.644197Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Extracting query from uri /api/upload
2023-05-18T08:16:51.644765Z  INFO tokio-runtime-worker pact_mock_server::hyper_server: Received request HTTP Request ( method: POST, path: /api/upload, query: None, headers: Some({"accept": ["application/json", "text/plain", 
"*/*"], "content-type": ["multipart/form-data; boundary=axios-1.3.4-boundary-DSxfzSsQnKAavHGBv3Gy-xrx8"], "content-length": ["981"], "host": ["127.0.0.1:54119"], "connection": ["close"], "accept-encoding": ["gzip", "compress", "deflate", "br"], "user-agent": ["axios/1.3.4"]}), body: Present(981 bytes, multipart/form-data;boundary=axios-1.3.4-boundary-DSxfzSsQnKAavHGBv3Gy-xrx8) )
2023-05-18T08:16:51.645823Z  INFO tokio-runtime-worker pact_matching: comparing to expected HTTP Request ( method: POST, path: /api/upload, query: None, headers: Some({"Content-Type": ["multipart/form-data; boundary=ZLyxFXoU09gGYGlJ"]}), body: Present(924 bytes, multipart/form-data) )
2023-05-18T08:16:51.646343Z DEBUG tokio-runtime-worker pact_matching:      body: '2D2D5A4C797846586F553039674759476C4ADA436F6E74656E742D44697370... (924 bytes)'
2023-05-18T08:16:51.646615Z DEBUG tokio-runtime-worker pact_matching:      matching_rules: MatchingRules { rules: {BODY: MatchingRuleCategory { name: BODY, rules: {DocPath { path_tokens: [Root, Field("file")], expr: "$.file" 
}: RuleList { rules: [ContentType("application/pdf")], rule_logic: And, cascaded: false }} }, PATH: MatchingRuleCategory { name: PATH, rules: {} }, HEADER: MatchingRuleCategory { name: HEADER, rules: {DocPath { path_tokens: [Root, Field("Content-Type")], expr: "Content-Type" }: RuleList { rules: [Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*")], rule_logic: And, cascaded: false }} }} }
2023-05-18T08:16:51.647368Z DEBUG tokio-runtime-worker pact_matching:      generators: Generators { categories: {} }
2023-05-18T08:16:51.647776Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing '/api/upload' to '/api/upload' ==> true cascaded=false matcher=Equality
2023-05-18T08:16:51.648105Z DEBUG tokio-runtime-worker pact_matching: expected content type = 'multipart/form-data', actual content type = 'multipart/form-data;boundary=axios-1.3.4-boundary-DSxfzSsQnKAavHGBv3Gy-xrx8'
2023-05-18T08:16:51.648663Z DEBUG tokio-runtime-worker pact_matching: content type header matcher = 'RuleList { rules: [Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*")], rule_logic: And, cascaded: false }'  
2023-05-18T08:16:51.649001Z DEBUG tokio-runtime-worker pact_plugin_driver::catalogue_manager: Looking for a content matcher for multipart/form-data
2023-05-18T08:16:51.649982Z DEBUG tokio-runtime-worker pact_matching: No content matcher defined for content type 'multipart/form-data', using core matcher implementation
2023-05-18T08:16:51.650272Z DEBUG tokio-runtime-worker pact_matching: Using body matcher for content type 'multipart/form-data'
2023-05-18T08:16:51.650549Z DEBUG tokio-runtime-worker pact_matching::binary_utils: matching MIME multipart contents
2023-05-18T08:16:51.650986Z DEBUG tokio-runtime-worker pact_matching::binary_utils: Comparing MIME field multipart 'file'
2023-05-18T08:16:51.651291Z DEBUG tokio-runtime-worker pact_matching::binary_utils: Calling match_values for path $.file
2023-05-18T08:16:51.651574Z DEBUG tokio-runtime-worker pact_matching::binary_utils: FilePart: comparing binary data to 'Some("application/pdf")' using ContentType("application/pdf")
2023-05-18T08:16:51.652470Z DEBUG tokio-runtime-worker pact_matching::binary_utils: Matching binary contents by content type: expected 'application/pdf', detected 'text/plain' -> false
2023-05-18T08:16:51.652737Z DEBUG tokio-runtime-worker pact_models::content_types: Detecting content type from byte contents
2023-05-18T08:16:51.653087Z DEBUG tokio-runtime-worker pact_matching::binary_utils: Comparing 'MimeFile { name: "file", content_type: Some("application/pdf"), filename: "empty.pdf", data: b"%PDF-1.4\n%\xd3\xeb\xe9\xe1\n1 0 obj\n<</Title (Untitled document)\n/Producer (Skia/PDF m115 Google Docs Renderer)>>\nendobj\n3 0 obj\n<</ca 1\n/BM /Normal>>\nendobj\n4 0 obj\n<</Length 84>> stream\n1 0 0 -1 0 792 cm\nq\n.75 0 0 .75 0 0 cm\n1 1 1 RG 1 1 1 rg\n/G3 gs\n0 0 816 1056 re\nf\nQ\n\nendstream\nendobj\n2 0 obj\n<</Type /Page\n/Resources <</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n/ExtGState <</G3 3 0 R>>>>\n/MediaBox [0 0 612 792]\n/Contents 4 0 R\n/StructParents 0\n/Parent 5 0 R>>\nendobj\n5 0 obj\n<</Type /Pages\n/Count 1\n/Kids [2 0 R]>>\nendobj\n6 0 obj\n<</Type /Catalog\n/Pages 5 0 R>>\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000015 00000 n \n0000000277 00000 n \n0000000108 00000 n \n0000000145 00000 n \n0000000465 00000 n \n0000000520 00000 n \ntrailer\n<</Size 7\n/Root 6 0 R\n/Info 1 0 R>>\nstartxref\n567\n%%EOF\n" }' to 'MimeFile { name: "file", content_type: Some("application/pdf"), filename: "blob", data: b"%PDF-1.4\n%\xd3\xeb\xe9\xe1\n1 0 obj\n<</Title (Untitled document)\n/Producer (Skia/PDF m115 Google Docs Renderer)>>\nendobj\n3 0 obj\n<</ca 1\n/BM /Normal>>\nendobj\n4 0 obj\n<</Length 84>> stream\n1 0 0 -1 0 792 cm\nq\n.75 0 0 .75 0 0 cm\n1 1 1 RG 1 1 1 rg\n/G3 gs\n0 0 816 1056 re\nf\nQ\n\nendstream\nendobj\n2 0 obj\n<</Type /Page\n/Resources <</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]\n/ExtGState <</G3 3 0 R>>>>\n/MediaBox [0 
0 612 792]\n/Contents 4 0 R\n/StructParents 0\n/Parent 5 0 R>>\nendobj\n5 0 obj\n<</Type /Pages\n/Count 1\n/Kids [2 0 R]>>\nendobj\n6 0 obj\n<</Type /Catalog\n/Pages 5 0 R>>\nendobj\nxref\n0 7\n0000000000 65535 f \n0000000015 00000 n \n0000000277 00000 n \n0000000108 00000 n \n0000000145 00000 n \n0000000465 00000 n \n0000000520 00000 n \ntrailer\n<</Size 7\n/Root 6 0 R\n/Info 1 0 R>>\nstartxref\n567\n%%EOF\n" }' at path '$.file' -> Err([BodyMismatch { path: "$.file", expected: None, actual: None, mismatch: "MIME part 'file': Expected binary contents to have content type 'application/pdf' but detected contents was 'text/plain'" }])
2023-05-18T08:16:51.655837Z DEBUG tokio-runtime-worker pact_matching::matchers: String -> String: comparing 'multipart/form-data; boundary=ZLyxFXoU09gGYGlJ' to 'multipart/form-data; boundary=axios-1.3.4-boundary-DSxfzSsQnKAavHGBv3Gy-xrx8' ==> true cascaded=false matcher=Regex("multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*")
2023-05-18T08:16:51.656453Z DEBUG tokio-runtime-worker pact_matching: --> Mismatches: [BodyMismatch { path: "$.file", expected: None, actual: None, mismatch: "MIME part 'file': Expected binary contents to have content type 'application/pdf' but detected contents was 'text/plain'" }]
2023-05-18T08:16:51.657515Z DEBUG tokio-runtime-worker pact_mock_server::hyper_server: Request did not match: Request did not match - HTTP Request ( method: POST, path: /api/upload, query: None, headers: Some({"Content-Type": ["multipart/form-data; boundary=ZLyxFXoU09gGYGlJ"]}), body: Present(924 bytes, multipart/form-data) )    0) $.file -> MIME part 'file': Expected binary contents to have content type 'application/pdf' but detected contents was 'text/plain'
2023-05-18T08:16:51.659274Z DEBUG tokio-runtime-worker hyper::proto::h1::io: flushed 2111 bytes
2023-05-18T08:16:51.666657Z 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-05-18T08:16:51.667054Z DEBUG ThreadId(01) pact_mock_server::server_manager: Shutting down mock server with ID 46ce9b3e-800a-492b-88fc-7335e03b3065 - MockServerMetrics { requests: 1 }
2023-05-18T08:16:51.667407Z DEBUG ThreadId(01) pact_mock_server::mock_server: Mock server 46ce9b3e-800a-492b-88fc-7335e03b3065 shutdown - MockServerMetrics { requests: 1 }
2023-05-18T08:16:51.667432Z DEBUG tokio-runtime-worker hyper::server::shutdown: signal received, starting graceful shutdown
[10:16:51.669] ERROR (29032): pact@11.0.2: Test failed for the following reasons:

  Mock server failed with the following mismatches:

        0) The following request was incorrect:

                POST /api/upload

                         1.0    $.file: MIME part 'file': Expected binary contents to have content type 'application/pdf' but detected contents was 'text/plain'

 FAIL  tests/UploadPdf.test.ts (7.282 s)
  POST /api/upload
    × upload data and return 201 (214 ms)

  ● POST /api/upload › upload data and return 201

    expect(received).resolves.not.toBeNull()

    Received promise rejected instead of resolved
    Rejected to value: [AxiosError: Request failed with status code 500]

      32 |
      33 |     return provider.executeTest(async (mockServer) => {
    > 34 |       await expect(
         |             ^
      35 |         uploadService.uploadPdf({
      36 |           baseUrl: mockServer.url,
      37 |           file: new Blob([PDF_FILE_DATA], {

      at expect (node_modules/expect/build/index.js:105:15)
      at tests/UploadPdf.test.ts:34:13
      at tests/UploadPdf.test.ts:27:71
      at Object.<anonymous>.__awaiter (tests/UploadPdf.test.ts:23:12)
      at tests/UploadPdf.test.ts:33:54
      at PactV3.<anonymous> (node_modules/@pact-foundation/src/v3/pact.ts:201:19)
      at step (node_modules/@pact-foundation/pact/src/v3/pact.js:33:23)
      at Object.next (node_modules/@pact-foundation/pact/src/v3/pact.js:14:53)
      at node_modules/@pact-foundation/pact/src/v3/pact.js:8:71
      at Object.<anonymous>.__awaiter (node_modules/@pact-foundation/pact/src/v3/pact.js:4:12)
      at PactV3.Object.<anonymous>.PactV3.executeTest (node_modules/@pact-foundation/pact/src/v3/pact.js:153:16)
      at Object.<anonymous> (tests/UploadPdf.test.ts:33:21)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        7.671 s
Ran all test suites.
@spawluk-zartis spawluk-zartis added the bug Indicates an unexpected problem or unintended behavior label May 18, 2023
@mefellows
Copy link
Member

Thanks for the repro.

I also get the x-tex issue on my MacOSX also. It depends on the mime database available on the OS.

			 1.0 	$.file: MIME part 'file': Expected binary contents to have content type 'application/pdf' but detected contents was 'text/x-tex'

@uglyog FYI.

I think in this case, you're best falling back to not using that method, and using something like this instead:

  it("upload data and return 201", () => {
    provider
      .uponReceiving("a request to upload pdf")
      .withRequest({
       method: "POST",
       path: "/api/upload",
       headers: {
          "Content-Type": MatchersV3.regex("multipart\/form-data; boundary=.*", "multipart/form-data; boundary=axios-1.3.4-boundary-Py9nQHSit6xdTOTPzJ2U6vH54"),
          "Content-Length": `981`, // Ideally this is also set, you could probably get it from the FormData object
       },
       body: "--boundary......" // see pseudo-code here, albeit it's probably not strictly that valuable: https://github.com/pact-foundation/pact-net/issues/410#issuecomment-1457853792
      })
      .willRespondWith({
        status: 204
      });

@mefellows mefellows added the triage This issue is yet to be triaged by a maintainer label Jul 12, 2023
@corwestermaniddink
Copy link

corwestermaniddink commented Oct 20, 2023

Latest pact versions.

Same with image/png file:
BodyMismatch { path: "$.file['content-type']", expected: Some(b"image/png"), actual: Some(b"application/octet-stream"), mismatch: "MIME part 'file': header 'content-type': Expected 'application/octet-stream' to be equal to 'image/png'" }]

interaction('A request for POST an App', ({ provider, execute }) => { const PNG_FILE_PATH = path.join(__dirname, "archie.png"); console.log(__dirname ) beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientModule], providers: [AppsHttpService], }); provider .given('Apps API available') .uponReceiving('a request to create an app through AppsHttpService') .withRequestMultipartFileUpload( { method: "POST", path: /apps, body: {"title":"test.nl","thumbnail_url":"archie.png","image_url":"archie.png","url":{"ios":"test.nl","android":"test.nl","web":"test.nl"}} }, "application/octet-stream", PNG_FILE_PATH, "file" ) .willRespondWith({ status: 200, }) } );

When I change the "application/octet-stream" to "image/png", I got the error other way around:
BodyTypeMismatch { expected: "image/png", actual: "application/octet-stream", mismatch: "MIME part 'file': Expected a body of 'image/png' but the actual content type was 'application/octet-stream'"

@mefellows Can this please be fixed? :)

@mefellows
Copy link
Member

Thanks for contributing to the discussion, there is no need to ask for it to be fixed though. Clearly there is a bug that needs fixing.

You're welcome to help fix it, or use the suggested workaround. We'll get to a fix when we can.

@corwestermaniddink
Copy link

@mefellows I did some extra testing, but this is a pact-js-core issue in my opinion. If I change this test with a .png image file, the same error occures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Indicates an unexpected problem or unintended behavior triage This issue is yet to be triaged by a maintainer
Projects
None yet
Development

No branches or pull requests

3 participants