diff --git a/traverson4j-core/src/main/java/uk/co/autotrader/traverson/http/SimpleMultipartBody.java b/traverson4j-core/src/main/java/uk/co/autotrader/traverson/http/SimpleMultipartBody.java index 290e7ed..ebdeabf 100644 --- a/traverson4j-core/src/main/java/uk/co/autotrader/traverson/http/SimpleMultipartBody.java +++ b/traverson4j-core/src/main/java/uk/co/autotrader/traverson/http/SimpleMultipartBody.java @@ -1,5 +1,6 @@ package uk.co.autotrader.traverson.http; +import java.io.InputStream; import java.util.Arrays; /** @@ -41,6 +42,7 @@ public String getContentType() { public static class BodyPart { private final String name; private final byte[] data; + private final InputStream inputStream; private final String contentType; private final String filename; @@ -56,6 +58,22 @@ public BodyPart(String name, byte[] data, String contentType, String filename) { this.data = Arrays.copyOf(data, data.length); this.contentType = contentType; this.filename = filename; + this.inputStream = null; + } + + /** + * Constructs a BodyPart + * @param name see {@link BodyPart#getName()} + * @param inputStream see {@link BodyPart#getData()} + * @param contentType see {@link BodyPart#getContentType()} + * @param filename see {@link BodyPart#getFilename()} + */ + public BodyPart(String name, InputStream inputStream, String contentType, String filename) { + this.name = name; + this.inputStream = inputStream; + this.contentType = contentType; + this.filename = filename; + this.data = null; } /** @@ -70,7 +88,18 @@ public String getName() { * @return the raw data for this body part */ public byte[] getData() { - return Arrays.copyOf(data, data.length); + if (data == null) { + return null; + } else { + return Arrays.copyOf(data, data.length); + } + } + + /** + * @return the input stream for this body part + */ + public InputStream getInputStream() { + return inputStream; } /** diff --git a/traverson4j-core/src/test/java/uk/co/autotrader/traverson/http/multipart/BodyPartTest.java b/traverson4j-core/src/test/java/uk/co/autotrader/traverson/http/multipart/BodyPartTest.java index e02a8aa..0e91e37 100644 --- a/traverson4j-core/src/test/java/uk/co/autotrader/traverson/http/multipart/BodyPartTest.java +++ b/traverson4j-core/src/test/java/uk/co/autotrader/traverson/http/multipart/BodyPartTest.java @@ -5,12 +5,15 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.co.autotrader.traverson.http.SimpleMultipartBody; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(MockitoExtension.class) class BodyPartTest { + @Test void init_GivenValues_SetsProperties() { SimpleMultipartBody.BodyPart multipart = new SimpleMultipartBody.BodyPart("name", "data".getBytes(StandardCharsets.UTF_8), "contentType", "filename"); @@ -19,5 +22,17 @@ void init_GivenValues_SetsProperties() { assertThat(multipart.getData()).isEqualTo("data".getBytes(StandardCharsets.UTF_8)); assertThat(multipart.getContentType()).isEqualTo("contentType"); assertThat(multipart.getFilename()).isEqualTo("filename"); + assertThat(multipart.getInputStream()).isNull(); + } + + @Test + void init_GivenValuesWithInputStream_SetsProperties() throws IOException { + SimpleMultipartBody.BodyPart multipart = new SimpleMultipartBody.BodyPart("name", new ByteArrayInputStream("data".getBytes(StandardCharsets.UTF_8)), "contentType", "filename"); + + assertThat(multipart.getName()).isEqualTo("name"); + assertThat(multipart.getData()).isNull(); + assertThat(multipart.getContentType()).isEqualTo("contentType"); + assertThat(multipart.getFilename()).isEqualTo("filename"); + assertThat(multipart.getInputStream().readAllBytes()).isEqualTo("data".getBytes(StandardCharsets.UTF_8)); } } diff --git a/traverson4j-hc5/src/main/java/uk/co/autotrader/traverson/http/entity/MultipartEntityConverter.java b/traverson4j-hc5/src/main/java/uk/co/autotrader/traverson/http/entity/MultipartEntityConverter.java index d038ff5..6b09b8c 100644 --- a/traverson4j-hc5/src/main/java/uk/co/autotrader/traverson/http/entity/MultipartEntityConverter.java +++ b/traverson4j-hc5/src/main/java/uk/co/autotrader/traverson/http/entity/MultipartEntityConverter.java @@ -12,7 +12,12 @@ class MultipartEntityConverter implements HttpEntityConverter { public HttpEntity toEntity(Body body) { MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); for (SimpleMultipartBody.BodyPart bodyPart : ((SimpleMultipartBody) body).getContent()) { - multipartEntityBuilder.addBinaryBody(bodyPart.getName(), bodyPart.getData(), ContentType.create(bodyPart.getContentType()), bodyPart.getFilename()); + + if (bodyPart.getData() != null) { + multipartEntityBuilder.addBinaryBody(bodyPart.getName(), bodyPart.getData(), ContentType.create(bodyPart.getContentType()), bodyPart.getFilename()); + } else { + multipartEntityBuilder.addBinaryBody(bodyPart.getName(), bodyPart.getInputStream(), ContentType.create(bodyPart.getContentType()), bodyPart.getFilename()); + } } return multipartEntityBuilder.build(); } diff --git a/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/IntegrationTest.java b/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/IntegrationTest.java index a469321..31eb105 100644 --- a/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/IntegrationTest.java +++ b/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/IntegrationTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; import uk.co.autotrader.traverson.Traverson; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -106,6 +107,26 @@ void requestBody_MultipartBodyIsSerializedAndPostedCorrectly() { assertThat(response.getStatusCode()).isEqualTo(202); } + @Test + void requestBody_MultipartBodyFromInputStreamIsSerializedAndPostedCorrectly() { + byte[] data = new byte[]{0x00, 0x01, 0x02}; + InputStream inputStream = new ByteArrayInputStream(data); + wireMockServer.stubFor(post("/records") + .withMultipartRequestBody(aMultipart() + .withName("my-body-part") + .withHeader("Content-Type", equalTo("application/octet-stream")) + .withBody(binaryEqualTo(data)) + ) + .willReturn(WireMock.status(202))); + SimpleMultipartBody.BodyPart bodyPart = new SimpleMultipartBody.BodyPart("my-body-part", inputStream, "application/octet-stream", "my-file"); + SimpleMultipartBody multipartBody = new SimpleMultipartBody(bodyPart); + Response response = traverson.from("http://localhost:8089/records") + .post(multipartBody); + + wireMockServer.verify(1, postRequestedFor(urlEqualTo("/records"))); + assertThat(response.getStatusCode()).isEqualTo(202); + } + @Test void basicAuthentication_ReactsToUnauthorizedStatusAndAuthenticateHeader() { wireMockServer.stubFor(get("/restricted-area") diff --git a/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/entity/BodyPartEntityConverterTest.java b/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/entity/BodyPartEntityConverterTest.java index b0705dc..198c216 100644 --- a/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/entity/BodyPartEntityConverterTest.java +++ b/traverson4j-hc5/src/test/java/uk/co/autotrader/traverson/http/entity/BodyPartEntityConverterTest.java @@ -7,6 +7,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import uk.co.autotrader.traverson.http.SimpleMultipartBody; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; @@ -38,4 +39,20 @@ void toEntity_GivenTextBody_ReturnsStringEntity() throws Exception { .matches(Pattern.compile(".*Content-Type: contenttype.*", Pattern.DOTALL | Pattern.MULTILINE)) .matches(Pattern.compile(".*data.*", Pattern.DOTALL | Pattern.MULTILINE)); } + + @Test + void toEntity_GivenTextBodyFromInputStream_ReturnsStringEntity() throws Exception { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + HttpEntity entity = converter.toEntity(new SimpleMultipartBody(new SimpleMultipartBody.BodyPart("file", new ByteArrayInputStream("data".getBytes(StandardCharsets.UTF_8)), "contentType", "filename"))); + + assertThat(entity.getContentType()).matches(Pattern.compile("multipart/form-data; charset=ISO-8859-1; boundary=.*")); + entity.writeTo(outputStream); + String content = new String(outputStream.toByteArray(), StandardCharsets.UTF_8); + assertThat(content) + .matches(Pattern.compile(".*name=\"file\".*", Pattern.DOTALL | Pattern.MULTILINE)) + .matches(Pattern.compile(".*filename=\"filename\".*", Pattern.DOTALL | Pattern.MULTILINE)) + .matches(Pattern.compile(".*Content-Type: contenttype.*", Pattern.DOTALL | Pattern.MULTILINE)) + .matches(Pattern.compile(".*data.*", Pattern.DOTALL | Pattern.MULTILINE)); + } }