pact-foundation / pact-jvm

JVM version of Pact. Enables consumer driven contract testing, providing a mock service and DSL for the consumer project, and interaction playback and verification for the service provider project.
https://docs.pact.io
Apache License 2.0
1.08k stars 479 forks source link

Prevent dynamic boundaries on multipart/form-data pacts #1792

Closed marcozet closed 4 months ago

marcozet commented 5 months ago

We have a scenario where we need to upload a bunch of files using post with Content-Type multipart/form-data.

So the request body looks like...

--m5owueqDDBp_nPEBOX-JvuMixxacXChqVMnc6
Content-Disposition: form-data; name="files"; filename="foo.txt"
Content-Type: text/plain

foo
--m5owueqDDBp_nPEBOX-JvuMixxacXChqVMnc6
Content-Disposition: form-data; name="files"; filename="bar.txt"
Content-Type: text/plain

bar
--m5owueqDDBp_nPEBOX-JvuMixxacXChqVMnc6--

and the Pact is generated as...

@Pact(consumer = "consumer-service", provider = "file-upload-service")
public RequestResponsePact upload(PactDslWithProvider builder) throws IOException {
    DslPart responseBody = newJsonBody((body) -> {
                body.uuid("uploadId")
    })
    .build();

    return builder.given("001: no data available")
            .uponReceiving("request to upload a file")
            .path(BASE_URI + "/uploads")
            .method("POST")
            .headers(Map.of("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE))
            .withFileUpload(
                    "files",
                    "bar.txt",
                    "application/octet-stream",
                    new ClassPathResource("foo.txt").getContentAsByteArray())
            .withFileUpload(
                    "file",
                    "foo.txt",
                    "application/octet-stream",
                    new ClassPathResource("bar.txt").getContentAsByteArray())
            .willRespondWith()
            .status(HttpStatus.OK.value())
            .body(responseBody)
            .toPact();
}

No issues so far.

We use the Pact Broker / Pact webhook workflow, so we publish the Pact file to the broker and the provider verification is triggered via the webhook when the content of the Pact file has changed (on contract_content_changed event).

But unfortunately, the generated and published Pact file differs from test run to test run, as the boundary in the request body that marks the various parts is generated dynamically.

As a result, the Pact broker triggers the provider verification for each consumer build, even though the Pact itself has not been changed (except for the boundary attribute).

Can I set the boundary attribute statically so that the Pact doesn't change?

I found a similar problem here: https://github.com/pact-foundation/pact-net/issues/410#issuecomment-1457853792

rholshausen commented 5 months ago

If you use the MultipartEntityBuilder from the Apache HTTP client, you can set the boundary to a fixed value (using setBoundary).

Example test using MultipartEntityBuilder https://github.com/pact-foundation/pact-jvm/blob/4752365ad30c9ba5640f37578d8b4a7b673d3653/consumer/junit/src/test/groovy/au/com/dius/pact/consumer/junit/ExampleMultipartSpec.groovy#L18

marcozet commented 4 months ago

OK, got it! Now it works... Thanks.