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

Encoding error when contract generating for request with binary body (for images) #1110

Open
weofferservice opened this issue Jun 2, 2020 · 2 comments
Labels
bug Indicates an unexpected problem or unintended behavior

Comments

@weofferservice
Copy link

weofferservice commented Jun 2, 2020

When request has binary body (for images) contract is generating to JSON in UTF-8 encoding.
This is error, because UTF-8 irreversible.
You must use Base64 or ISO_8859_1
I found this bug and fix it.
I made pull request to fix this error (#1111).

I attached to this Issue my test data which helped me to find bug:

  1. face-with-smile.jpg
  2. PhotoQualityOkEndpointTest.java
package com.example;

import au.com.dius.pact.consumer.MockServer;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.annotations.Pact;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.api.Assertions;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.ConfigFileApplicationContextInitializer;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;

import static com.example.PactFisengineConstants.*;

@ExtendWith({PactConsumerTestExt.class, SpringExtension.class})
@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
@PactTestFor(providerName = PROVIDER, hostInterface = "localhost", port = "8080")
public class PhotoQualityOkEndpointTest {

    @Value("${cs.service.fisEngine.photoQualityEndpoint:#{new Object()}}") // mandatory value
    private String photoQualityEndpoint;

    private static byte[] photoQualityOkRequestBody;
    private static String photoQualityOkResponseBody;

    static {
        try {
            photoQualityOkRequestBody = PhotoQualityOkEndpointTest.class.getResourceAsStream("/photos/face-with-smile.jpg").readAllBytes();
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        try {
            photoQualityOkResponseBody = new String(
                    PhotoQualityOkEndpointTest.class.getResourceAsStream("/fis-responses/picture-checker-ok.json").readAllBytes(),
                    StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    @Pact(consumer = CONSUMER)
    public RequestResponsePact createPhotoQualityEndpointPact(PactDslWithProvider builder) throws JSONException, IOException {
        photoQualityEndpoint = "/" + StringUtils.strip(photoQualityEndpoint, "/");

        PactDslJsonBody pactDslOkJsonBody = new PactDslJsonBody();
        JSONObject jsonOkObject = new JSONObject(photoQualityOkResponseBody);
        ReflectionTestUtils.setField(pactDslOkJsonBody, "body", jsonOkObject);
        pactDslOkJsonBody.stringMatcher("version", ".+", "1");

        return builder

                // Request
                .given(PHOTO_QUALITY_STATE)
                .uponReceiving("Photo Quality Endpoint - OK")
                .path(photoQualityEndpoint)
                .withFileUpload(
                        "photo",
                        "face-with-smile.jpg",
                        MediaType.IMAGE_JPEG_VALUE,
                        photoQualityOkRequestBody)
                .method(HttpMethod.POST.name())

                // Response
                .willRespondWith()
                .status(HttpStatus.OK.value())
                .headers(
                        Map.ofEntries(
                                Map.entry(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
                        )
                )
                .body(pactDslOkJsonBody)

                .toPact();
    }

    @Test
    public void photoQuality(MockServer mockServer) throws JSONException {
        //Given
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);

        RestTemplate restTemplate = new RestTemplate();
        
        JSONObject expectedResponseBody =
                new JSONObject()
                        .put("version", "1")
                        .put("issues", new JSONArray())
                        .put("errorMessage", "")
                        .put("status", 0);

        MultiValueMap<String, Object> body
                = new LinkedMultiValueMap<>();
        body.add("photo", new ByteArrayResource(photoQualityOkRequestBody) {
            @Override
            public String getFilename() {
                return "face-with-smile.jpg"; // to set Content-Type header to "image/jpeg"
            }
        });

        //When
        ResponseEntity<String> response = restTemplate
                .postForEntity(
                        mockServer.getUrl() + photoQualityEndpoint,
                        new HttpEntity<>(body, httpHeaders),
                        String.class);

        //Then
        Assertions.assertThat(response)
                .isNotNull();
        Assertions.assertThat(response.getStatusCode())
                .isEqualTo(HttpStatus.OK);
        Assertions.assertThat(response.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE))
                .isTrue();
        Assertions.assertThat(response.getHeaders().get(HttpHeaders.CONTENT_TYPE))
                .contains(MediaType.APPLICATION_JSON_UTF8_VALUE);

        JSONAssert.assertEquals(
                expectedResponseBody.toString(),
                response.getBody(),
                true);
    }

}
  1. picture-checker-ok.json
{
  "issues": [],
  "status": 0,
  "errorMessage": "",
  "version": "14.0.0.119"
}
@uglyog
Copy link
Member

uglyog commented Jun 13, 2020

There is another problem, the multipart body is not being base64 encoded before being written to the pact file

uglyog pushed a commit that referenced this issue Jun 14, 2020
@uglyog uglyog added the bug Indicates an unexpected problem or unintended behavior label Jun 14, 2020
@uglyog
Copy link
Member

uglyog commented Jun 14, 2020

I think this has been addressed now. If you run PostImageBodyTest.groovy and take the body from the persisted pact file, base64 decode it and remove the multipart header it loads correctly in an image editor.

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
Projects
None yet
Development

No branches or pull requests

2 participants