Skip to content

Commit

Permalink
Merge pull request #4013 from jamezp/RESTEASY-3445
Browse files Browse the repository at this point in the history
[RESTEASY-3445] Add a test to ensure the headers for EntityPart's are…
  • Loading branch information
jamezp committed Feb 6, 2024
2 parents 84002cc + 2f290df commit 32d1a82
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 7 deletions.
6 changes: 6 additions & 0 deletions providers/multipart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>resteasy-json-p-provider</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>resteasy-undertow-cdi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,19 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import jakarta.json.Json;
import jakarta.json.JsonArray;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.Consumes;
Expand All @@ -46,6 +53,8 @@
import jakarta.ws.rs.core.GenericEntity;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

import org.junit.jupiter.api.AfterAll;
Expand Down Expand Up @@ -104,11 +113,9 @@ public void form() throws Exception {
Assertions.fail(msg);
}
EntityPart part = find(entityParts, "received-content");
Assertions.assertNotNull(part, () -> getMessage(entityParts));
Assertions.assertEquals("test content", part.getContent(String.class));

part = find(entityParts, "added-content");
Assertions.assertNotNull(part, () -> getMessage(entityParts));
Assertions.assertEquals("test added content", part.getContent(String.class));
}
}
Expand Down Expand Up @@ -264,14 +271,183 @@ public void injection() throws Exception {
}
}

/**
* Tests sending {@code multipart/form-data} content as a {@link EntityPart List<EntityPart>}. Three parts are sent
* and processed by the resource returning all the headers from the request.
* <p>
* The result from the REST endpoint is a list of {@link EntityPart}'s which content the content from the entity
* sent, plus any header beginning with "test-".
* </p>
*
* @throws Exception if an error occurs in the test
*/
@Test
public void echo() throws Exception {
try (Client client = ClientBuilder.newClient()) {
final List<EntityPart> multipart = List.of(
EntityPart.withName("entity-part")
.content("test entity part")
.mediaType(MediaType.TEXT_PLAIN_TYPE)
.header("test-entity-1", "part1")
.header("test-content-type", MediaType.TEXT_PLAIN)
.build(),
EntityPart.withName("string-part")
.content("test string")
.mediaType(MediaType.TEXT_PLAIN_TYPE)
.header("test-entity-2", "part2")
.header("test-content-type", MediaType.TEXT_PLAIN)
.build(),
EntityPart.withName("input-stream-part")
.content("test input stream".getBytes(StandardCharsets.UTF_8))
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
.header("test-entity-3", "part3")
.header("test-content-type", MediaType.APPLICATION_OCTET_STREAM)
.build());
try (
Response response = client.target(INSTANCE.configuration()
.baseUriBuilder()
.path("test/echo"))
.request(MediaType.MULTIPART_FORM_DATA_TYPE)
.post(Entity.entity(new GenericEntity<>(multipart) {
}, MediaType.MULTIPART_FORM_DATA))) {
Assertions.assertEquals(Response.Status.OK, response.getStatusInfo());
final List<EntityPart> entityParts = response.readEntity(new GenericType<>() {
});
if (entityParts.size() != 3) {
final String msg = "Expected 3 entries got " +
entityParts.size() +
'.' +
System.lineSeparator() +
getMessage(entityParts);
Assertions.fail(msg);
}

EntityPart part = find(entityParts, "received-entity-part");
checkEntity(part, "test entity part");
MultivaluedMap<String, String> headers = part.getHeaders();
checkHeader(headers, "test-entity-1", "part1");
checkHeader(headers, "test-content-type", MediaType.TEXT_PLAIN);

part = find(entityParts, "received-string-part");
checkEntity(part, "test string");
headers = part.getHeaders();
checkHeader(headers, "test-entity-2", "part2");
checkHeader(headers, "test-content-type", MediaType.TEXT_PLAIN);

part = find(entityParts, "received-input-stream-part");
checkEntity(part, "test input stream");
headers = part.getHeaders();
checkHeader(headers, "test-entity-3", "part3");
checkHeader(headers, "test-content-type", MediaType.APPLICATION_OCTET_STREAM);
}
}
}

/**
* Tests sending {@code multipart/form-data} content as a {@link EntityPart List<EntityPart>}. Three parts are sent
* and processed by the resource returning all the headers from the request.
* <p>
* The result from the REST endpoint is a JSON object containing the headers for each entry.
* <br>
* Example:
* <code>
* <pre>
* {
* "entity-part": {
* "Content-Disposition": [
* "form-data; name=\"entity-part\""
* ],
* "Content-Type": [
* "text/plain"
* ],
* "test-content-type": [
* "text/plain"
* ],
* "test-entity-1": [
* "part1"
* ]
* }
* }
* </pre>
* </code>
* </p>
*
* @throws Exception if an error occurs in the test
*/
@Test
public void echoHeaders() throws Exception {
try (Client client = ClientBuilder.newClient()) {
final List<EntityPart> multipart = List.of(
EntityPart.withName("entity-part")
.content("test entity part")
.mediaType(MediaType.TEXT_PLAIN_TYPE)
.header("test-entity-1", "part1")
.header("test-content-type", MediaType.TEXT_PLAIN, MediaType.APPLICATION_OCTET_STREAM)
.build(),
EntityPart.withName("string-part")
.content("test string")
.mediaType(MediaType.TEXT_PLAIN_TYPE)
.header("test-entity-2", "part2")
.header("test-content-type", MediaType.TEXT_PLAIN)
.build(),
EntityPart.withName("input-stream-part")
.content("test input stream".getBytes(StandardCharsets.UTF_8))
.mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
.header("test-entity-3", "part3")
.header("test-content-type", MediaType.APPLICATION_OCTET_STREAM)
.build());
try (
Response response = client.target(INSTANCE.configuration()
.baseUriBuilder()
.path("test/echo-headers"))
.request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(new GenericEntity<>(multipart) {
}, MediaType.MULTIPART_FORM_DATA))) {
Assertions.assertEquals(Response.Status.OK, response.getStatusInfo());

final JsonObject json = response.readEntity(JsonObject.class);
JsonObject part = json.getJsonObject("entity-part");
checkHeader(part, "test-entity-1", "part1");
checkHeader(part, "test-content-type", MediaType.TEXT_PLAIN, MediaType.APPLICATION_OCTET_STREAM);

part = json.getJsonObject("string-part");
checkHeader(part, "test-entity-2", "part2");
checkHeader(part, "test-content-type", MediaType.TEXT_PLAIN);

part = json.getJsonObject("input-stream-part");
checkHeader(part, "test-entity-3", "part3");
checkHeader(part, "test-content-type", MediaType.APPLICATION_OCTET_STREAM);
}
}
}

private static void checkEntity(final List<EntityPart> entityParts, final String name, final String expectedText)
throws IOException {
final EntityPart part = find(entityParts, name);
Assertions.assertNotNull(part,
() -> String.format("Failed to find entity part %s in: %s", name, getMessage(entityParts)));
checkEntity(part, expectedText);
}

private static void checkEntity(final EntityPart part, final String expectedText)
throws IOException {
Assertions.assertEquals(expectedText, part.getContent(String.class));
}

private static void checkHeader(final JsonObject json, final String name, final String... expectedValues) {
final JsonArray array = json.getJsonArray(name);
Assertions.assertNotNull(array, () -> String.format("Failed to find array %s in %s", name, json));
final List<String> found = array.stream()
.filter(v -> v.getValueType() == JsonValue.ValueType.STRING)
.map(v -> ((JsonString) v).getString())
.toList();
Assertions.assertIterableEquals(List.of(expectedValues), found);
}

private static void checkHeader(final MultivaluedMap<String, String> headers, final String name,
final String expectedValue) {
Assertions.assertEquals(expectedValue, headers.getFirst(name),
() -> String.format("Missing header name \"%s\" in %s", name, formatHeaders(new StringBuilder(), headers)));
}

private static String getMessage(final List<EntityPart> parts) {
final StringBuilder msg = new StringBuilder();
final Iterator<EntityPart> iter = parts.iterator();
Expand All @@ -296,6 +472,16 @@ private static String getMessage(final List<EntityPart> parts) {
return msg.toString();
}

private static StringBuilder formatHeaders(final StringBuilder builder, final MultivaluedMap<String, String> headers) {
for (var entry : headers.entrySet()) {
builder.append(entry.getKey())
.append(": ")
.append(entry.getValue())
.append(System.lineSeparator());
}
return builder;
}

private static String toString(final InputStream in) {
try {
// try-with-resources fails here due to a bug in the
Expand All @@ -316,13 +502,13 @@ private static String toString(final InputStream in) {
}
}

private static EntityPart find(final Collection<EntityPart> parts, final String name) {
private static EntityPart find(final List<EntityPart> parts, final String name) {
for (EntityPart part : parts) {
if (name.equals(part.getName())) {
return part;
}
}
return null;
throw new RuntimeException("Could not find entity part named " + name + " - " + getMessage(parts));
}

@ApplicationPath("/")
Expand Down Expand Up @@ -402,5 +588,53 @@ public List<EntityPart> injected(@FormParam("content") final String string,
.mediaType(MediaType.TEXT_PLAIN_TYPE)
.build());
}

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.MULTIPART_FORM_DATA)
@Path("/echo")
public List<EntityPart> echo(final List<EntityPart> entityParts) throws IOException {
final List<EntityPart> resultParts = new ArrayList<>(entityParts.size());
for (EntityPart entityPart : entityParts) {
final EntityPart part = EntityPart.withName("received-" + entityPart.getName())
.content(entityPart.getContent())
.mediaType(entityPart.getMediaType())
.fileName(entityPart.getFileName().orElse(null))
.headers(filterHeaders(entityPart.getHeaders()))
.build();
resultParts.add(part);
}
return resultParts;
}

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
@Path("/echo-headers")
public Response echoHeaders(final List<EntityPart> entityParts) {
final JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
for (EntityPart entityPart : entityParts) {
final JsonObjectBuilder headerObjectBuilder = Json.createObjectBuilder();
entityPart.getHeaders().forEach((name, values) -> {
final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (String value : values) {
arrayBuilder.add(value);
}
headerObjectBuilder.add(name, arrayBuilder);
});
objectBuilder.add(entityPart.getName(), headerObjectBuilder);
}
return Response.ok(objectBuilder.build()).build();
}

private static MultivaluedMap<String, String> filterHeaders(final MultivaluedMap<String, String> headers) {
final MultivaluedMap<String, String> filtered = new MultivaluedHashMap<>();
for (var entry : headers.entrySet()) {
if (entry.getKey().startsWith("test-")) {
filtered.put(entry.getKey(), entry.getValue());
}
}
return filtered;
}
}
}

0 comments on commit 32d1a82

Please sign in to comment.