diff --git a/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java b/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java index 1e02c5c1..de644e19 100644 --- a/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Contributors to the Eclipse Foundation + * Copyright 2025 Contributors to the Eclipse Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.net.URI; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -30,6 +31,7 @@ import org.eclipse.microprofile.rest.client.RestClientBuilder; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; import org.jboss.arquillian.testng.Arquillian; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; @@ -43,16 +45,16 @@ import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonValue; +import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.BadRequestException; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.client.ClientRequestContext; -import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.core.EntityPart; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; /** * @author James R. Perkins @@ -60,12 +62,44 @@ @RunAsClient public class EntityPartTest extends Arquillian { + @ArquillianResource + private URI uri; + @Deployment public static WebArchive createDeployment() { return ShrinkWrap.create(WebArchive.class, EntityPart.class.getSimpleName() + ".war") + .addClasses(FileUploadResource.class, FileUploadApplication.class) .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); } + @ApplicationPath("/") + public static class FileUploadApplication extends jakarta.ws.rs.core.Application { + } + + @Path("/entitypart") + public static class FileUploadResource { + + @POST + @Path("upload") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response uploadFile(List entityParts) throws IOException { + final JsonArrayBuilder jsonBuilder = Json.createArrayBuilder(); + for (EntityPart part : entityParts) { + final JsonObjectBuilder jsonPartBuilder = Json.createObjectBuilder(); + jsonPartBuilder.add("name", part.getName()); + if (part.getFileName().isPresent()) { + jsonPartBuilder.add("fileName", part.getFileName().get()); + } else { + throw new BadRequestException("No file name for entity part " + part); + } + jsonPartBuilder.add("content", part.getContent(String.class)); + jsonBuilder.add(jsonPartBuilder); + } + return Response.status(201).entity(jsonBuilder.build()).build(); + } + } + /** * Tests that a single file is upload. The response is a simple JSON response with the file information. * @@ -153,16 +187,14 @@ public void uploadMultipleFiles() throws Exception { } } - private static FileManagerClient createClient() { + private FileManagerClient createClient() { return RestClientBuilder.newBuilder() - // Fake URI as we use a filter to short-circuit the request - .baseUri("http://localhost:8080") - .register(new FileManagerFilter()) + .baseUri(UriBuilder.fromUri(uri).path("entitypart").build()) .build(FileManagerClient.class); } @Consumes(MediaType.MULTIPART_FORM_DATA) - @Produces(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) public interface FileManagerClient extends AutoCloseable { @POST @@ -170,31 +202,4 @@ public interface FileManagerClient extends AutoCloseable { Response uploadFile(List entityParts) throws IOException; } - public static class FileManagerFilter implements ClientRequestFilter { - - @Override - public void filter(final ClientRequestContext requestContext) throws IOException { - if (requestContext.getMethod().equals("POST")) { - // Download the file - @SuppressWarnings("unchecked") - final List entityParts = (List) requestContext.getEntity(); - final JsonArrayBuilder jsonBuilder = Json.createArrayBuilder(); - for (EntityPart part : entityParts) { - final JsonObjectBuilder jsonPartBuilder = Json.createObjectBuilder(); - jsonPartBuilder.add("name", part.getName()); - if (part.getFileName().isPresent()) { - jsonPartBuilder.add("fileName", part.getFileName().get()); - } else { - throw new BadRequestException("No file name for entity part " + part); - } - jsonPartBuilder.add("content", part.getContent(String.class)); - jsonBuilder.add(jsonPartBuilder); - } - requestContext.abortWith(Response.status(201).entity(jsonBuilder.build()).build()); - } else { - requestContext - .abortWith(Response.status(Response.Status.BAD_REQUEST).entity("Invalid request").build()); - } - } - } } diff --git a/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/asynctests/AsyncEntityPartTest.java b/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/asynctests/AsyncEntityPartTest.java new file mode 100644 index 00000000..25ced1f1 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/asynctests/AsyncEntityPartTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 2025 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.eclipse.microprofile.rest.client.tck.asynctests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.testng.Assert; +import org.testng.annotations.Test; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.EntityPart; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; + +/** + * @author Neena Jacob + */ +@RunAsClient +public class AsyncEntityPartTest extends Arquillian { + + @ArquillianResource + private URI uri; + + @Deployment + public static WebArchive createDeployment() { + return ShrinkWrap.create(WebArchive.class, EntityPart.class.getSimpleName() + ".war") + .addClasses(FileUploadResource.class, FileUploadApplication.class) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml"); + } + + @ApplicationPath("/") + public static class FileUploadApplication extends jakarta.ws.rs.core.Application { + } + + @Path("/entitypart") + public static class FileUploadResource { + + @POST + @Path("upload") + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response uploadFile(List entityParts) throws IOException { + final JsonArrayBuilder jsonBuilder = Json.createArrayBuilder(); + for (EntityPart part : entityParts) { + final JsonObjectBuilder jsonPartBuilder = Json.createObjectBuilder(); + jsonPartBuilder.add("name", part.getName()); + if (part.getFileName().isPresent()) { + jsonPartBuilder.add("fileName", part.getFileName().get()); + } else { + throw new BadRequestException("No file name for entity part " + part); + } + jsonPartBuilder.add("content", part.getContent(String.class)); + jsonBuilder.add(jsonPartBuilder); + } + return Response.status(201).entity(jsonBuilder.build()).build(); + } + } + + /** + * Tests that a single file is upload. The response is a simple JSON response with the file information. + * + * @throws Exception + * if a test error occurs + */ + @Test + public void uploadFileAsync() throws Exception { + try (AsyncFileManagerClient client = createClient()) { + final byte[] content; + try (InputStream in = AsyncEntityPartTest.class.getResourceAsStream("/multipart/test-file1.txt")) { + Assert.assertNotNull(in, "Could not find /multipart/test-file1.txt"); + content = in.readAllBytes(); + } + // Send in an InputStream to ensure it works with an InputStream + final List files = List.of(EntityPart.withFileName("test-file1.txt") + .content(new ByteArrayInputStream(content)) + .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE) + .build()); + + CompletionStage futureResponse = client.uploadFileAsync(files); + Response response = futureResponse.toCompletableFuture().get(); + + try { + Assert.assertEquals(201, response.getStatus()); + final JsonArray jsonArray = response.readEntity(JsonArray.class); + Assert.assertNotNull(jsonArray); + Assert.assertEquals(jsonArray.size(), 1); + final JsonObject json = jsonArray.getJsonObject(0); + Assert.assertEquals(json.getString("name"), "test-file1.txt"); + Assert.assertEquals(json.getString("fileName"), "test-file1.txt"); + Assert.assertEquals(json.getString("content"), "This is a test file for file 1.\n"); + } finally { + response.close(); + } + } + } + + private AsyncFileManagerClient createClient() { + return RestClientBuilder.newBuilder() + .baseUri(UriBuilder.fromUri(uri).path("entitypart").build()) + .build(AsyncFileManagerClient.class); + } + + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public interface AsyncFileManagerClient extends AutoCloseable { + + @POST + @Path("upload") + CompletionStage uploadFileAsync(List entityParts); + } + +}