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