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

image upload contract test #961

Open ireinhart opened 4 years ago

ireinhart commented 4 years ago

Hello,

is it possible to contract test an image upload with pact? I have written in some different kind of languages (dart and jvm) a consumer test with image upload. The pact file is now valid. But at the verification stage I get an error from my "provider". After a few days (i think a hole week :-( ) of investigating, it looks like, that pact-provider-verifier (ruby) and mvn pact:verify send an invalid file to my API under test.

Should I give up?

Best regards, Ingo

Consumer test:

package au.com.dius.pact.consumer.junit

import au.com.dius.pact.core.model.PactSpecVersion
import au.com.dius.pact.core.model.annotations.Pact
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.core.model.RequestResponsePact
import org.apache.http.client.methods.RequestBuilder
import org.apache.http.entity.ContentType
import org.apache.http.entity.mime.HttpMultipartMode
import org.apache.http.entity.mime.MultipartEntityBuilder
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.junit.Rule
import org.junit.Test

class ExampleFileUploadSpec {

  @Rule
  @SuppressWarnings('PublicInstanceField')
  public final PactProviderRule mockProvider = new PactProviderRule('API', PactSpecVersion.V2, this)

  @Pact(provider = 'API', consumer= 'App')
  RequestResponsePact createPact(PactDslWithProvider builder) {
    File file = new File("..../pact-jvm/consumer/pact-jvm-consumer-junit/src/test/groovy/au/com/dius/pact/consumer/junit/200x5px.jpg")
    byte[] binaryContent = file.bytes
    builder
      .given("users and recipes for app requests")
      .uponReceiving('POST to `/v2/me/avatar`')
      .path('/v2/me/avatar')
      .method('POST')
      .headers("Authorization", "Bearer dummyOauthToken")
      .withFileUpload('avatar', '200x5px.jpg', 'application/octet-stream', binaryContent)
      .willRespondWith()
      .status(200)
      .body('avatar_updated', 'application/json')
      .toPact()
  }

  @Test
  @PactVerification
  void runTest() {
    CloseableHttpClient httpclient = HttpClients.createDefault()
    httpclient.withCloseable {
      def data = MultipartEntityBuilder.create()
        .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
        .addBinaryBody('avatar', '1,2,3,4\n5,6,7,8'.bytes, ContentType.create('application/octet-stream'), '200x5px.jpg')
        .build()
      def request = RequestBuilder
        .post(mockProvider.url + '/v2/me/avatar')
        .addHeader("Authorization", "Bearer dummyOauthToken")
        .setEntity(data)
        .build()
      println('Executing request ' + request.requestLine)
      httpclient.execute(request)
    }
  }
}

my pact file:

{
  "provider": {
    "name": "API"
  },
  "consumer": {
    "name": "App"
  },
  "interactions": [
    {
      "description": "POST to `/v2/me/avatar`",
      "request": {
        "method": "POST",
        "path": "/v2/me/avatar",
        "headers": {
          "Authorization": "Bearer dummyOauthToken",
          "Content-Type": "multipart/form-data; boundary\u003djEgXaqOWy9dnD9xmpdaFBp00OouBOEys"
        },
        "body": "--jEgXaqOWy9dnD9xmpdaFBp00OouBOEys\r\nContent-Disposition: form-data; name\u003d\"avatar\"; filename\u003d\"200x5px.jpg\"\r\nContent-Type: application/octet-stream\r\n\r\n����\u0000\u0010JFIF\u0000\u0001\u0001\u0001\u0000\u0001\u0000\u0001\u0000\u0000��\u0000C\u0000\u0003\u0002\u0002\u0002\u0002\u0002\u0003\u0002\u0002\u0002\u0003\u0003\u0003\u0003\u0004\u0006\u0004\u0004\u0004\u0004\u0004\b\u0006\u0006\u0005\u0006\t\b\n\n\t\b\t\t\n\f\u000f\f\n\u000b\u000e\u000b\t\t\r\u0011\r\u000e\u000f\u0010\u0010\u0011\u0010\n\f\u0012\u0013\u0012\u0010\u0013\u000f\u0010\u0010\u0010��\u0000C\u0001\u0003\u0003\u0003\u0004\u0003\u0004\b\u0004\u0004\b\u0010\u000b\t\u000b\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010\u0010��\u0000\u0011\b\u0000\u0005\u0000�\u0003\u0001\"\u0000\u0002\u0011\u0001\u0003\u0011\u0001��\u0000\u0015\u0000\u0001\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\t��\u0000\u0014\u0010\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000��\u0000\u0016\u0001\u0001\u0001\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0005\b��\u0000\u0014\u0011\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000��\u0000\f\u0003\u0001\u0000\u0002\u0011\u0003\u0011\u0000?\u0000�@5Z \u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0003��\r\n--jEgXaqOWy9dnD9xmpdaFBp00OouBOEys--\r\n",
        "matchingRules": {
          "$.headers.Content-Type": {
            "match": "regex",
            "regex": "multipart/form-data;(\\s*charset\u003d[^;]*;)?\\s*boundary\u003d.*"
          }
        }
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "body": "avatar_updated"
      },
      "providerState": "users"
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "2.0.0"
    },
    "pact-jvm": {
      "version": "4.0.2"
    }
  }
}

saved image on server:

????JFIF??C

??C

        ??"??   ???????
                       ??@5Z ??
pitschr commented 4 years ago

Your issue is not related Pact far as I know.

Storing binary data in a JSON file (e.g. which is generated by the Pact) is usually not a good idea and can cause some issues. It is better to use a Base64 encoding if you want to store binary data in a textual format like JSON. It is considered as safe for transport.

On provider side you may decode the base64 or assert against the base64 stream directly (without decoding it).

uglyog commented 4 years ago

I'm not sure there is much value in testing a file upload with Pact. The file contents is being sent in HTTP multi-part mime format, so that is not a format you have much control over and so can't really change things to break the upload. In essence you are testing the HTTP handling of your server.