From e794d5e5b5c9e3a6d90d8c28e061dcda00bfa3cb Mon Sep 17 00:00:00 2001
From: "James R. Perkins" <jperkins@redhat.com>
Date: Tue, 11 Jun 2024 12:06:07 -0700
Subject: [PATCH] Add a TCK test to ensure the MP REST Client implementation
 works with the Jakarta REST EntityPart.

Signed-off-by: James R. Perkins <jperkins@redhat.com>
---
 .../rest/client/tck/EntityPartTest.java       | 200 ++++++++++++++++++
 .../main/resources/multipart/test-file1.txt   |   1 +
 .../main/resources/multipart/test-file2.txt   |   1 +
 3 files changed, 202 insertions(+)
 create mode 100644 tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java
 create mode 100644 tck/src/main/resources/multipart/test-file1.txt
 create mode 100644 tck/src/main/resources/multipart/test-file2.txt

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
new file mode 100644
index 00000000..1e02c5c1
--- /dev/null
+++ b/tck/src/main/java/org/eclipse/microprofile/rest/client/tck/EntityPartTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2024 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+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.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.json.JsonValue;
+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;
+
+/**
+ * @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
+ */
+@RunAsClient
+public class EntityPartTest extends Arquillian {
+
+    @Deployment
+    public static WebArchive createDeployment() {
+        return ShrinkWrap.create(WebArchive.class, EntityPart.class.getSimpleName() + ".war")
+                .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
+    }
+
+    /**
+     * 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 uploadFile() throws Exception {
+        try (FileManagerClient client = createClient()) {
+            final byte[] content;
+            try (InputStream in = EntityPartTest.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<EntityPart> files = List.of(EntityPart.withFileName("test-file1.txt")
+                    .content(new ByteArrayInputStream(content))
+                    .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                    .build());
+            try (Response response = client.uploadFile(files)) {
+                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");
+            }
+        }
+    }
+
+    /**
+     * Tests that two files are upload. The response is a simple JSON response with the file information.
+     *
+     * @throws Exception
+     *             if a test error occurs
+     */
+    @Test
+    public void uploadMultipleFiles() throws Exception {
+        try (FileManagerClient client = createClient()) {
+            final Map<String, byte[]> entityPartContent = new LinkedHashMap<>(2);
+            try (InputStream in = EntityPartTest.class.getResourceAsStream("/multipart/test-file1.txt")) {
+                Assert.assertNotNull(in, "Could not find /multipart/test-file1.txt");
+                entityPartContent.put("test-file1.txt", in.readAllBytes());
+            }
+            try (InputStream in = EntityPartTest.class.getResourceAsStream("/multipart/test-file2.txt")) {
+                Assert.assertNotNull(in, "Could not find /multipart/test-file2.txt");
+                entityPartContent.put("test-file2.txt", in.readAllBytes());
+            }
+            final List<EntityPart> files = entityPartContent.entrySet()
+                    .stream()
+                    .map((entry) -> {
+                        try {
+                            return EntityPart.withName(entry.getKey())
+                                    .fileName(entry.getKey())
+                                    .content(entry.getValue())
+                                    .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
+                                    .build();
+                        } catch (IOException e) {
+                            throw new UncheckedIOException(e);
+                        }
+                    })
+                    .collect(Collectors.toList());
+
+            try (Response response = client.uploadFile(files)) {
+                Assert.assertEquals(201, response.getStatus());
+                final JsonArray jsonArray = response.readEntity(JsonArray.class);
+                Assert.assertNotNull(jsonArray);
+                Assert.assertEquals(jsonArray.size(), 2);
+                // Don't assume the results are in a specific order
+                for (JsonValue value : jsonArray) {
+                    final JsonObject json = value.asJsonObject();
+                    if (json.getString("name").equals("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");
+                    } else if (json.getString("name").equals("test-file2.txt")) {
+                        Assert.assertEquals(json.getString("fileName"), "test-file2.txt");
+                        Assert.assertEquals(json.getString("content"), "This is a test file for file 2.\n");
+                    } else {
+                        Assert.fail(String.format("Unexpected entry %s in JSON response: %n%s", json, jsonArray));
+                    }
+                }
+            }
+        }
+    }
+
+    private static FileManagerClient createClient() {
+        return RestClientBuilder.newBuilder()
+                // Fake URI as we use a filter to short-circuit the request
+                .baseUri("http://localhost:8080")
+                .register(new FileManagerFilter())
+                .build(FileManagerClient.class);
+    }
+
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    @Produces(MediaType.MULTIPART_FORM_DATA)
+    public interface FileManagerClient extends AutoCloseable {
+
+        @POST
+        @Path("upload")
+        Response uploadFile(List<EntityPart> 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<EntityPart> entityParts = (List<EntityPart>) 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/resources/multipart/test-file1.txt b/tck/src/main/resources/multipart/test-file1.txt
new file mode 100644
index 00000000..f47a79b7
--- /dev/null
+++ b/tck/src/main/resources/multipart/test-file1.txt
@@ -0,0 +1 @@
+This is a test file for file 1.
diff --git a/tck/src/main/resources/multipart/test-file2.txt b/tck/src/main/resources/multipart/test-file2.txt
new file mode 100644
index 00000000..8d2bc3ff
--- /dev/null
+++ b/tck/src/main/resources/multipart/test-file2.txt
@@ -0,0 +1 @@
+This is a test file for file 2.