From abe8db571e8a7c68cb2a448bc5ffc5b13b3cd98d Mon Sep 17 00:00:00 2001
From: alaskowski <alaskowski@ethz.ch>
Date: Wed, 8 Mar 2023 16:05:15 +0100
Subject: [PATCH] SSDM-13251: Added support for list and read methods to
 client. Refactored and reformatted tests.

---
 .../ethz/sis/afsclient/client/AfsClient.java  | 70 +++++++++++++------
 .../sis/afsclient/client/AfsClientTest.java   | 45 +++++++++---
 .../sis/afsclient/client/DummyHttpServer.java | 18 ++++-
 .../server/impl/ApiServerAdapter.java         |  5 +-
 .../afsserver/worker/proxy/ExecutorProxy.java |  2 +-
 .../ch/ethz/sis/afsserver/ApiClientTest.java  | 66 ++++++++++++++---
 .../sis/afsserver/core/PublicApiTest.java     | 48 ++++++++-----
 7 files changed, 192 insertions(+), 62 deletions(-)

diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java
index 63dc36dbcbe..141c393222a 100644
--- a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java
@@ -21,6 +21,7 @@ import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper;
 import lombok.NonNull;
 
+
 public final class AfsClient implements PublicAPI
 {
 
@@ -89,22 +90,15 @@ public final class AfsClient implements PublicAPI
     @Override
     public @NonNull Boolean isSessionValid() throws Exception
     {
-        if (getSessionToken() == null)
-        {
-            throw new IllegalStateException("No session information detected!");
-        }
+        validateSessionToken();
         return request("GET", "isSessionValid", Map.of("sessionToken", getSessionToken()));
     }
 
     @Override
     public @NonNull Boolean logout() throws Exception
     {
-        if (getSessionToken() == null)
-        {
-            throw new IllegalStateException("No session information detected!");
-        }
-//      Boolean result = request("POST", "logout", Map.of(), getSessionToken().getBytes());
-        Boolean result = request("POST", "logout", Map.of("sessionToken", getSessionToken()));
+        validateSessionToken();
+        Boolean result = request("POST", "logout", Map.of(), getSessionToken().getBytes());
         setSessionToken(null);
         return result;
     }
@@ -113,14 +107,21 @@ public final class AfsClient implements PublicAPI
     public @NonNull List<File> list(@NonNull final String owner, @NonNull final String source,
             @NonNull final Boolean recursively) throws Exception
     {
-        return null;
+        validateSessionToken();
+        return request("GET", "list",
+                Map.of("owner", owner, "source", source, "recursively",
+                        recursively.toString(), "sessionToken", getSessionToken()));
     }
 
     @Override
     public @NonNull byte[] read(@NonNull final String owner, @NonNull final String source,
             @NonNull final Long offset, @NonNull final Integer limit) throws Exception
     {
-        return new byte[0];
+        validateSessionToken();
+        return request("GET", "read",
+                Map.of("owner", owner, "source", source, "offset",
+                        offset.toString(), "limit", limit.toString(), "sessionToken",
+                        getSessionToken()));
     }
 
     @Override
@@ -229,16 +230,22 @@ public final class AfsClient implements PublicAPI
         final int statusCode = httpResponse.statusCode();
         if (statusCode >= 200 && statusCode < 300)
         {
-            final ApiResponse response =
-                    jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()),
-                            ApiResponse.class);
-
-            if (response.getError() != null)
+            if (!httpResponse.headers().map().containsKey("content-type"))
             {
-                throw ClientExceptions.API_ERROR.getInstance(response.getError());
-            } else
+                throw new IllegalArgumentException(
+                        "Server error HTTP response. Missing content-type");
+            }
+            String content = httpResponse.headers().map().get("content-type").get(0);
+
+            switch (content)
             {
-                return (T) response.getResult();
+                case "application/json":
+                    return parseJsonResponse(httpResponse);
+                case "application/octet-stream":
+                    return (T) httpResponse.body();
+                default:
+                    throw new IllegalArgumentException(
+                            "Client error HTTP response. Unsupported content-type received.");
             }
         } else if (statusCode >= 400 && statusCode < 500)
         {
@@ -252,4 +259,27 @@ public final class AfsClient implements PublicAPI
         }
     }
 
+    private <T> T parseJsonResponse(final HttpResponse<byte[]> httpResponse) throws Exception
+    {
+        final ApiResponse response =
+                jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()),
+                        ApiResponse.class);
+
+        if (response.getError() != null)
+        {
+            throw ClientExceptions.API_ERROR.getInstance(response.getError());
+        } else
+        {
+            return (T) response.getResult();
+        }
+    }
+
+    private void validateSessionToken()
+    {
+        if (getSessionToken() == null)
+        {
+            throw new IllegalStateException("No session information detected!");
+        }
+    }
+
 }
diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java
index a9ec352489f..56156c84813 100644
--- a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java
+++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java
@@ -14,11 +14,14 @@ public class AfsClientTest
     private static DummyHttpServer httpServer;
 
     private AfsClient afsClient;
+
     private static final int HTTP_SERVER_PORT = 8085;
+
     private static final String HTTP_SERVER_PATH = "/fileserver";
 
     @Before
-    public void setUp() throws Exception {
+    public void setUp() throws Exception
+    {
         httpServer = new DummyHttpServer(HTTP_SERVER_PORT, HTTP_SERVER_PATH);
         httpServer.start();
         afsClient = new AfsClient(
@@ -27,7 +30,8 @@ public class AfsClientTest
     }
 
     @After
-    public void tearDown() {
+    public void tearDown()
+    {
         httpServer.stop();
     }
 
@@ -45,9 +49,10 @@ public class AfsClientTest
     {
         try
         {
-           afsClient.isSessionValid();
+            afsClient.isSessionValid();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (IllegalStateException e)
+        {
             assertThat(e.getMessage(), containsString("No session information detected!"));
         }
     }
@@ -71,7 +76,8 @@ public class AfsClientTest
         {
             afsClient.logout();
             fail();
-        } catch (IllegalStateException e) {
+        } catch (IllegalStateException e)
+        {
             assertThat(e.getMessage(), containsString("No session information detected!"));
         }
     }
@@ -91,27 +97,42 @@ public class AfsClientTest
     }
 
     @Test
-    public void testList() throws Exception
+    public void list_methodIsGet() throws Exception
     {
+        login();
+
+        httpServer.setNextResponse("{\"result\": null}");
+        afsClient.list("", "", true);
+
+        assertEquals("GET", httpServer.getHttpExchange().getRequestMethod());
     }
 
     @Test
-    public void testRead()throws Exception
+    public void read_methodIsGet() throws Exception
     {
+        login();
+
+        byte[] data = "ABCD".getBytes();
+        httpServer.setNextResponse(data);
+
+        byte[] result = afsClient.read("admin", "/", 0L, 1000);
+
+        assertEquals("GET", httpServer.getHttpExchange().getRequestMethod());
+        assertArrayEquals(data, result);
     }
 
     @Test
-    public void testWrite()throws Exception
+    public void testWrite() throws Exception
     {
     }
 
     @Test
-    public void testDelete()throws Exception
+    public void testDelete() throws Exception
     {
     }
 
     @Test
-    public void testCopy()throws Exception
+    public void testCopy() throws Exception
     {
     }
 
@@ -145,4 +166,8 @@ public class AfsClientTest
     {
     }
 
+    private void login() throws Exception {
+        afsClient.login("test", "test");
+    }
+
 }
\ No newline at end of file
diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java
index f4d9337fce7..dbc97199894 100644
--- a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java
+++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java
@@ -35,7 +35,9 @@ public final class DummyHttpServer
 
     private static final String DEFAULT_RESPONSE = "{\"result\": \"success\"}";
 
-    private String nextResponse = DEFAULT_RESPONSE;
+    private byte[] nextResponse = DEFAULT_RESPONSE.getBytes();
+    private String nextResponseType = "application/json";
+
     private HttpExchange httpExchange;
 
     public DummyHttpServer(int httpServerPort, String httpServerPath) throws IOException
@@ -47,8 +49,10 @@ public final class DummyHttpServer
         {
             public void handle(HttpExchange exchange) throws IOException
             {
-                byte[] response = nextResponse.getBytes();
+                byte[] response = nextResponse;
+                exchange.getResponseHeaders().set("content-type", nextResponseType);
                 exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length);
+
                 exchange.getResponseBody().write(response);
                 exchange.close();
                 httpExchange = exchange;
@@ -67,11 +71,19 @@ public final class DummyHttpServer
     }
 
     public void setNextResponse(String response)
+    {
+        this.nextResponse = response.getBytes();
+        this.nextResponseType = "application/json";
+    }
+
+    public void setNextResponse(byte[] response)
     {
         this.nextResponse = response;
+        this.nextResponseType = "application/octet-stream";
     }
 
-    public HttpExchange getHttpExchange() {
+    public HttpExchange getHttpExchange()
+    {
         return httpExchange;
     }
 
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
index d57eecdc4f8..ae64cc19675 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
@@ -156,6 +156,7 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler
                 }
             }
 
+            // Process body parameters
             switch (method) {
                 case "write":
                     methodParameters.put("data", requestBody);
@@ -166,9 +167,11 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler
                     methodParameters.put("userId", credentials[0]);
                     methodParameters.put("password", credentials[1]);
                     break;
+                case "logout":
+                    sessionToken = new String(requestBody, UTF_8);
+                    break;
             }
 
-
             ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken,
                     interactiveSessionKey, transactionManagerKey);
             Response response =
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
index ab041bb13c8..a219a700ab2 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
@@ -64,7 +64,7 @@ public class ExecutorProxy extends AbstractProxy {
     //
 
     public String getPath(String owner, String source) {
-        return IOUtils.PATH_SEPARATOR + owner.toString() + source;
+        return String.join(""+IOUtils.PATH_SEPARATOR, "", owner.toString(), source);
     }
 
     @Override
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java
index e9065b60241..1d3d61be291 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java
@@ -21,9 +21,15 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.*;
 
+import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Paths;
 import java.util.List;
+import java.util.UUID;
 
+import ch.ethz.sis.afsapi.dto.File;
+import ch.ethz.sis.shared.io.IOUtils;
+import org.apache.commons.io.FileUtils;
 import org.junit.*;
 
 import ch.ethz.sis.afs.manager.TransactionConnection;
@@ -32,6 +38,7 @@ import ch.ethz.sis.afsserver.server.Server;
 import ch.ethz.sis.afsserver.server.observer.impl.DummyServerObserver;
 import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter;
 import ch.ethz.sis.shared.startup.Configuration;
+import org.junit.rules.TemporaryFolder;
 
 public final class ApiClientTest
 {
@@ -43,6 +50,16 @@ public final class ApiClientTest
 
     private static String httpServerPath;
 
+    private static String storageRoot;
+
+    public static final String FILE_A = "A.txt";
+
+    public static final byte[] DATA = "ABCD".getBytes();
+
+    public static final String FILE_B = "B.txt";
+
+    public static String owner = UUID.randomUUID().toString();
+
     @BeforeClass
     public static void classSetUp() throws Exception
     {
@@ -55,25 +72,33 @@ public final class ApiClientTest
                 configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort);
         httpServerPath =
                 configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri);
+        storageRoot = configuration.getStringProperty(AtomicFileSystemServerParameter.storageRoot);
+    }
+
+    @AfterClass
+    public static void classTearDown() throws Exception
+    {
+        afsServer.shutdown(true);
     }
 
     @Before
     public void setUp() throws Exception
     {
+        String testDataRoot = IOUtils.getPath(storageRoot, owner.toString());
+        IOUtils.createDirectories(testDataRoot);
+        String testDataFile = IOUtils.getPath(testDataRoot, FILE_A);
+        IOUtils.createFile(testDataFile);
+        IOUtils.write(testDataFile, 0, DATA);
+
         afsClient = new AfsClient(
                 new URI("http", null, "localhost", httpServerPort,
                         httpServerPath, null, null));
     }
 
-    private String login() throws Exception
-    {
-        return afsClient.login("test", "test");
-    }
-
-    @AfterClass
-    public static void classTearDown() throws Exception
+    @After
+    public void deleteTestData() throws IOException
     {
-        afsServer.shutdown(true);
+        IOUtils.delete(storageRoot);
     }
 
     @Test
@@ -128,4 +153,29 @@ public final class ApiClientTest
         assertTrue(result);
     }
 
+    @Test
+    public void list_getsDataListFromTemporaryFolder() throws Exception
+    {
+        login();
+
+        List<File> list = afsClient.list(owner, "", Boolean.TRUE);
+        assertEquals(1, list.size());
+        assertEquals(FILE_A, list.get(0).getName());
+    }
+
+    @Test
+    public void read_getsDataFromTemporaryFile() throws Exception {
+        login();
+
+        byte[] bytes = afsClient.read(owner, FILE_A, 0L, DATA.length);
+        assertArrayEquals(DATA, bytes);
+    }
+
+
+
+    private String login() throws Exception
+    {
+        return afsClient.login("test", "test");
+    }
+
 }
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
index cb7acab3d13..4af73090c05 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
@@ -33,27 +33,27 @@ import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-public abstract class PublicApiTest extends AbstractTest {
+public abstract class PublicApiTest extends AbstractTest
+{
 
     public abstract PublicAPI getPublicAPI() throws Exception;
 
     public static final String ROOT = IOUtils.PATH_SEPARATOR_AS_STRING;
-//    public static final String DIR_A = "A";
-//    public static final String DIR_B = "B";
+
     public static final String FILE_A = "A.txt";
+
     public static final byte[] DATA = "ABCD".getBytes();
+
     public static final String FILE_B = "B.txt";
-//    public static final String DIR_A_PATH = IOUtils.PATH_SEPARATOR + getPath(DIR_A);
-//    public static final String DIR_B_PATH = IOUtils.PATH_SEPARATOR + getPath(DIR_B);
-//    public static final String FILE_A_PATH = getPath(DIR_A_PATH, FILE_A);
-//    public static final String FILE_B_PATH = getPath(DIR_B_PATH, FILE_B);
 
     public String owner = UUID.randomUUID().toString();
 
     @Before
-    public void createTestData() throws IOException {
+    public void createTestData() throws IOException
+    {
         String storageRoot = ServerClientEnvironmentFS.getInstance()
-                .getDefaultServerConfiguration().getStringProperty(AtomicFileSystemServerParameter.storageRoot);
+                .getDefaultServerConfiguration()
+                .getStringProperty(AtomicFileSystemServerParameter.storageRoot);
         String testDataRoot = IOUtils.getPath(storageRoot, owner.toString());
         IOUtils.createDirectories(testDataRoot);
         String testDataFile = IOUtils.getPath(testDataRoot, FILE_A);
@@ -62,43 +62,51 @@ public abstract class PublicApiTest extends AbstractTest {
     }
 
     @After
-    public void deleteTestData() throws IOException {
+    public void deleteTestData() throws IOException
+    {
         String storageRoot = ServerClientEnvironmentFS.getInstance()
-                .getDefaultServerConfiguration().getStringProperty(AtomicFileSystemServerParameter.storageRoot);
+                .getDefaultServerConfiguration()
+                .getStringProperty(AtomicFileSystemServerParameter.storageRoot);
         IOUtils.delete(storageRoot);
         String writeAheadLogRoot = ServerClientEnvironmentFS.getInstance()
-                .getDefaultServerConfiguration().getStringProperty(AtomicFileSystemServerParameter.writeAheadLogRoot);
+                .getDefaultServerConfiguration()
+                .getStringProperty(AtomicFileSystemServerParameter.writeAheadLogRoot);
         IOUtils.delete(writeAheadLogRoot);
     }
 
     @Test
-    public void list() throws Exception {
+    public void list() throws Exception
+    {
         List<File> list = getPublicAPI().list(owner, ROOT, Boolean.TRUE);
         assertEquals(1, list.size());
         assertEquals(FILE_A, list.get(0).getName());
     }
 
     @Test
-    public void read() throws Exception {
+    public void read() throws Exception
+    {
         byte[] bytes = getPublicAPI().read(owner, "/" + FILE_A, 0L, DATA.length);
         assertArrayEquals(DATA, bytes);
     }
 
     @Test(expected = RuntimeException.class)
-    public void read_big_failure() throws Exception {
+    public void read_big_failure() throws Exception
+    {
         byte[] bytes = getPublicAPI().read(owner, "/" + FILE_A, 0L, Integer.MAX_VALUE);
         assertArrayEquals(DATA, bytes);
     }
 
     @Test
-    public void write() throws Exception {
+    public void write() throws Exception
+    {
         getPublicAPI().write(owner, "/" + FILE_B, 0L, DATA, IOUtils.getMD5(DATA));
         byte[] bytes = getPublicAPI().read(owner, "/" + FILE_B, 0L, DATA.length);
         assertArrayEquals(DATA, bytes);
     }
 
     @Test
-    public void delete() throws Exception {
+    public void delete() throws Exception
+    {
         Boolean deleted = getPublicAPI().delete(owner, "/" + FILE_A);
         assertTrue(deleted);
         List<File> list = getPublicAPI().list(owner, ROOT, Boolean.TRUE);
@@ -106,14 +114,16 @@ public abstract class PublicApiTest extends AbstractTest {
     }
 
     @Test
-    public void copy() throws Exception {
+    public void copy() throws Exception
+    {
         getPublicAPI().copy(owner, "/" + FILE_A, owner, "/" + FILE_B);
         byte[] bytes = getPublicAPI().read(owner, "/" + FILE_B, 0L, DATA.length);
         assertArrayEquals(DATA, bytes);
     }
 
     @Test
-    public void move() throws Exception {
+    public void move() throws Exception
+    {
         getPublicAPI().move(owner, "/" + FILE_A, owner, "/" + FILE_B);
         List<File> list = getPublicAPI().list(owner, ROOT, Boolean.TRUE);
         assertEquals(1, list.size());
-- 
GitLab