diff --git a/pom.xml b/pom.xml index 802954471a4931c42cef2e2d88a24415a4a8037f..d33966b4f649cc4a7abac52be75361ade4c00870 100644 --- a/pom.xml +++ b/pom.xml @@ -53,6 +53,25 @@ <artifactId>commons-net</artifactId> <version>3.8.0</version> </dependency> + <!-- Test --> + <dependency> + <groupId>org.apache.ftpserver</groupId> + <artifactId>ftpserver-core</artifactId> + <version>1.2.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>3.12.4</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.11.0</version> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/src/main/java/fil/sr2/flopbox/FTPResource.java b/src/main/java/fil/sr2/flopbox/FTPResource.java index 2d857c3a006c0d08f61f1818cea5b1c44d208afe..6872e185af6a8200d46d591268a51a0674c6c91b 100644 --- a/src/main/java/fil/sr2/flopbox/FTPResource.java +++ b/src/main/java/fil/sr2/flopbox/FTPResource.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import fil.sr2.flopbox.utils.*; +/** The class with the paths */ @Path("/ftps") public class FTPResource { diff --git a/src/main/java/fil/sr2/flopbox/FTPServerRepository.java b/src/main/java/fil/sr2/flopbox/FTPServerRepository.java index 73cd3b8351b9675902d6337c062834c4761e5d7d..2bec8ddd67811464274b3e299ca190f6c26a77c6 100644 --- a/src/main/java/fil/sr2/flopbox/FTPServerRepository.java +++ b/src/main/java/fil/sr2/flopbox/FTPServerRepository.java @@ -4,6 +4,7 @@ import java.util.*; import fil.sr2.flopbox.utils.*; +/** Take care of the FTP servers and their aliases */ public class FTPServerRepository { private static FTPServerRepository instance = new FTPServerRepository(); private Map<String, FTPServerConfig> serverMap = new HashMap<>(); diff --git a/src/main/java/fil/sr2/flopbox/FTPService.java b/src/main/java/fil/sr2/flopbox/FTPService.java index 82e6cf0c9a4994b548b1ef7495b9f2848c7fc372..2104c537af44e0a1f014532d4999fbc84e96354e 100644 --- a/src/main/java/fil/sr2/flopbox/FTPService.java +++ b/src/main/java/fil/sr2/flopbox/FTPService.java @@ -17,6 +17,7 @@ import java.io.ByteArrayInputStream; import fil.sr2.flopbox.utils.*; +/** Class with methods used in FTPResource */ public class FTPService { public FtpNode getResourceTree(String alias, String path, String user, String pass) diff --git a/src/main/java/fil/sr2/flopbox/MyResource.java b/src/main/java/fil/sr2/flopbox/MyResource.java deleted file mode 100644 index 994c3193b01070cb0375ffefe52edd5abb8af2be..0000000000000000000000000000000000000000 --- a/src/main/java/fil/sr2/flopbox/MyResource.java +++ /dev/null @@ -1,25 +0,0 @@ -package fil.sr2.flopbox; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; - -/** - * Root resource (exposed at "myresource" path) - */ -@Path("myresource") -public class MyResource { - - /** - * Method handling HTTP GET requests. The returned object will be sent - * to the client as "text/plain" media type. - * - * @return String that will be returned as a text/plain response. - */ - @GET - @Produces(MediaType.TEXT_PLAIN) - public String getIt() { - return "Got it!"; - } -} diff --git a/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java b/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java index 2d28ee5b1bc317528d6cbe6e8da69f9a8d5c5781..d4002ca7bf810f786eddb4547765b6a921155d9d 100644 --- a/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java +++ b/src/main/java/fil/sr2/flopbox/utils/AuthFilter.java @@ -10,6 +10,7 @@ import jakarta.ws.rs.core.*; import jakarta.ws.rs.container.*; import jakarta.ws.rs.ext.Provider; +/** Handles user authentication to the Flopbox proxy */ @Provider @Priority(Priorities.AUTHENTICATION) public class AuthFilter implements ContainerRequestFilter { diff --git a/src/main/java/fil/sr2/flopbox/utils/FTPException.java b/src/main/java/fil/sr2/flopbox/utils/FTPException.java index de05f079e78d31c6a3d563dd3574550d28d0b7bb..7ad4df04188a830a59c6d1eeff042a2562b9ccee 100644 --- a/src/main/java/fil/sr2/flopbox/utils/FTPException.java +++ b/src/main/java/fil/sr2/flopbox/utils/FTPException.java @@ -1,5 +1,6 @@ package fil.sr2.flopbox.utils; +/** An exception for FTP problems */ public class FTPException extends Exception { private final int status; diff --git a/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java b/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java index 7e95f471d0387b052cdb3c2bdab2ea05e07b37b1..ebd5bb8b7de628b0dc85071486366b9c09a8689c 100644 --- a/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java +++ b/src/main/java/fil/sr2/flopbox/utils/FTPServerConfig.java @@ -1,5 +1,6 @@ package fil.sr2.flopbox.utils; +/** Represents the configuration of a FTP server */ public class FTPServerConfig { private String alias; private String host; diff --git a/src/test/java/fil/sr2/flopbox/EmbeddedFTPServer.java b/src/test/java/fil/sr2/flopbox/EmbeddedFTPServer.java new file mode 100644 index 0000000000000000000000000000000000000000..b9ef916cd875c0b39db461174141a5dc67270a34 --- /dev/null +++ b/src/test/java/fil/sr2/flopbox/EmbeddedFTPServer.java @@ -0,0 +1,49 @@ +package fil.sr2.flopbox; + +import org.apache.ftpserver.FtpServer; +import org.apache.ftpserver.FtpServerFactory; +import org.apache.ftpserver.ftplet.FtpException; +import org.apache.ftpserver.listener.ListenerFactory; +import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory; +import org.apache.ftpserver.usermanager.impl.BaseUser; +import java.io.File; + +public class EmbeddedFTPServer { + private FtpServerFactory serverFactory; + private FtpServer server; + private PropertiesUserManagerFactory userManagerFactory; + private int port; + + public EmbeddedFTPServer(int port) { + this.port = port; + serverFactory = new FtpServerFactory(); + ListenerFactory listenerFactory = new ListenerFactory(); + listenerFactory.setPort(port); + serverFactory.addListener("default", listenerFactory.createListener()); + userManagerFactory = new PropertiesUserManagerFactory(); + serverFactory.setUserManager(userManagerFactory.createUserManager()); + } + + public void start() throws FtpException { + server = serverFactory.createServer(); + server.start(); + } + + public void stop() { + if (server != null) { + server.stop(); + } + } + + public void addUser(String username, String password, File homeDir) throws FtpException { + BaseUser user = new BaseUser(); + user.setName(username); + user.setPassword(password); + user.setHomeDirectory(homeDir.getAbsolutePath()); + serverFactory.getUserManager().save(user); + } + + public int getPort() { + return port; + } +} diff --git a/src/test/java/fil/sr2/flopbox/FTPResourceTest.java b/src/test/java/fil/sr2/flopbox/FTPResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ad313d8fa1c3aaef16bebfe9b949eea5885ba493 --- /dev/null +++ b/src/test/java/fil/sr2/flopbox/FTPResourceTest.java @@ -0,0 +1,336 @@ +package fil.sr2.flopbox; + +import fil.sr2.flopbox.utils.FTPException; +import org.apache.commons.io.FileUtils; +import org.junit.*; +import org.mockito.Mockito; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.UriInfo; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.lang.reflect.Method; + +import static org.junit.Assert.*; + +import fil.sr2.flopbox.*; +import fil.sr2.flopbox.utils.*; + +public class FTPResourceTest { + private static EmbeddedFTPServer embeddedFTPServer; + private static File ftpHomeDir; + private static final String USER = "testuser"; + private static final String PASS = "testpass"; + private static final String ALIAS = "testserver"; + private static int ftpPort; + + @BeforeClass + public static void setUpClass() throws Exception { + ftpHomeDir = Files.createTempDirectory("ftp-test").toFile(); + ftpPort = 2123; + embeddedFTPServer = new EmbeddedFTPServer(ftpPort); + embeddedFTPServer.addUser(USER, PASS, ftpHomeDir); + embeddedFTPServer.start(); + } + + @AfterClass + public static void tearDownClass() throws Exception { + embeddedFTPServer.stop(); + FileUtils.deleteDirectory(ftpHomeDir); + } + + @Before + public void setUp() throws IOException { + // Reset repository and add test server + FTPServerRepository repo = FTPServerRepository.getInstance(); + repo.getAllServers().forEach(s -> repo.removeServer(s.getAlias())); + FTPServerConfig config = new FTPServerConfig(ALIAS, "localhost", ftpPort); + repo.addServer(config); + + // Create a test file in FTP home + File testFile = new File(ftpHomeDir, "testfile.txt"); + FileUtils.writeStringToFile(testFile, "test content", StandardCharsets.UTF_8); + } + + @After + public void tearDown() throws IOException { + // Clean FTP home directory + FileUtils.cleanDirectory(ftpHomeDir); + } + + @Test + public void testListFTPServers() { + FTPResource resource = new FTPResource(); + Response response = resource.listFTPServers(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + List<FTPServerConfig> servers = (List<FTPServerConfig>) response.getEntity(); + assertTrue(servers.stream().anyMatch(s -> ALIAS.equals(s.getAlias()))); + } + + @Test + public void testAddFTPServerDuplicate() { + FTPResource resource = new FTPResource(); + UriInfo uriInfo = Mockito.mock(UriInfo.class); + Mockito.when(uriInfo.getAbsolutePathBuilder()).thenReturn(new UriBuilderMock()); + + FTPServerConfig config = new FTPServerConfig(ALIAS, "localhost", 21); + Response response = resource.addFTPServer(config, uriInfo); + assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); + } + + @Test + public void testRemoveFTPServerSuccess() { + FTPResource resource = new FTPResource(); + Response response = resource.removeFTPServer(ALIAS); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + assertNull(FTPServerRepository.getInstance().getServer(ALIAS)); + } + + @Test + public void testUpdateFTPServerSuccess() { + FTPResource resource = new FTPResource(); + FTPServerConfig newConfig = new FTPServerConfig(ALIAS, "localhost", 2122); + Response response = resource.updateFTPServer(ALIAS, newConfig); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals(2122, FTPServerRepository.getInstance().getServer(ALIAS).getPort()); + } + + @Test + public void testGetFTPRoot() { + FTPResource resource = new FTPResource(); + Response response = resource.getFTPRoot(ALIAS, USER, PASS); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + FTPService.FtpNode root = (FTPService.FtpNode) response.getEntity(); + assertTrue(root.children.stream().anyMatch(node -> "testfile.txt".equals(node.name))); + } + + @Test + public void testGetFTPResourceFile() { + FTPResource resource = new FTPResource(); + Response response = resource.getFTPResource(ALIAS, "testfile.txt", USER, PASS); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals("text/plain", response.getHeaderString("Content-Type")); + assertTrue(((byte[]) response.getEntity()).length > 0); + } + + @Test + public void testUploadFile() throws IOException { + FTPResource resource = new FTPResource(); + String path = "uploaded.txt"; + InputStream stream = new ByteArrayInputStream("content".getBytes()); + Response response = resource.uploadFile(ALIAS, path, USER, PASS, stream); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + File uploaded = new File(ftpHomeDir, path); + assertTrue(uploaded.exists()); + } + + @Test + public void testDeleteResource() throws IOException { + File toDelete = new File(ftpHomeDir, "to_delete.txt"); + toDelete.createNewFile(); + FTPResource resource = new FTPResource(); + Response response = resource.deleteResource(ALIAS, "to_delete.txt", USER, PASS); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + assertFalse(toDelete.exists()); + } + + @Test + public void testSearchFiles() { + FTPResource resource = new FTPResource(); + Response response = resource.searchFiles("testfile.txt", USER, PASS); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Map<String, List<String>> results = (Map<String, List<String>>) response.getEntity(); + assertFalse(results.isEmpty()); + } + + @Test + public void testInvalidCredentials() { + FTPResource resource = new FTPResource(); + Response response = resource.getFTPRoot(ALIAS, "wrong", "wrong"); + assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), response.getStatus()); + } + + private static class UriBuilderMock extends UriBuilder { + // Add the missing method + @Override + public URI buildFromEncodedMap(Map<String, ?> values) { + return URI.create("http://localhost:8080/ftps"); + } + + @Override + public URI buildFromMap(Map<String, ?> values) { + return URI.create("http://localhost:8080/ftps"); + } + + @Override + public URI buildFromMap(Map<String, ?> values, boolean encodeSlashInPath) { + return URI.create("http://localhost:8080/ftps"); + } + + // Change parameter from Class<?> to raw Class + @Override + public UriBuilder path(Class resource) { + return this; + } + + // Change parameter from Class<?> to raw Class + @Override + public UriBuilder path(Class resource, String method) { + return this; + } + + @Override + public UriBuilder path(String path) { + return this; + } + + @Override + public URI build(Object... values) { + return URI.create("http://localhost:8080/ftps"); + } + + @Override + public URI build(Object[] values, boolean encodeSlashInPath) { + return URI.create("http://localhost:8080/ftps"); + } + + @Override + public URI buildFromEncoded(Object... values) { + return URI.create("http://localhost:8080/ftps"); + } + + @Override + public String toTemplate() { + return "http://localhost:8080/ftps"; + } + + @Override + public UriBuilder clone() { + return this; + } + + @Override + public UriBuilder uri(URI uri) { + return this; + } + + @Override + public UriBuilder uri(String uri) { + return this; + } + + @Override + public UriBuilder scheme(String scheme) { + return this; + } + + @Override + public UriBuilder schemeSpecificPart(String ssp) { + return this; + } + + @Override + public UriBuilder userInfo(String ui) { + return this; + } + + @Override + public UriBuilder host(String host) { + return this; + } + + @Override + public UriBuilder port(int port) { + return this; + } + + @Override + public UriBuilder replacePath(String path) { + return this; + } + + @Override + public UriBuilder path(Method method) { + return this; + } + + @Override + public UriBuilder segment(String... segments) { + return this; + } + + @Override + public UriBuilder replaceMatrix(String matrix) { + return this; + } + + @Override + public UriBuilder matrixParam(String name, Object... values) { + return this; + } + + @Override + public UriBuilder replaceMatrixParam(String name, Object... values) { + return this; + } + + @Override + public UriBuilder replaceQuery(String query) { + return this; + } + + @Override + public UriBuilder queryParam(String name, Object... values) { + return this; + } + + @Override + public UriBuilder replaceQueryParam(String name, Object... values) { + return this; + } + + @Override + public UriBuilder fragment(String fragment) { + return this; + } + + @Override + public UriBuilder resolveTemplate(String name, Object value) { + return this; + } + + @Override + public UriBuilder resolveTemplate(String name, Object value, boolean encodeSlashInPath) { + return this; + } + + @Override + public UriBuilder resolveTemplateFromEncoded(String name, Object value) { + return this; + } + + @Override + public UriBuilder resolveTemplates(Map<String, Object> templateValues) { + return this; + } + + @Override + public UriBuilder resolveTemplates(Map<String, Object> templateValues, boolean encodeSlashInPath) { + return this; + } + + @Override + public UriBuilder resolveTemplatesFromEncoded(Map<String, Object> templateValues) { + return this; + } + } +} diff --git a/src/test/java/fil/sr2/flopbox/MyResourceTest.javaTEMPO b/src/test/java/fil/sr2/flopbox/MyResourceTest.javaTEMPO deleted file mode 100644 index 79e09ec41c1f743aa358d9a6a39f6df830b7980a..0000000000000000000000000000000000000000 --- a/src/test/java/fil/sr2/flopbox/MyResourceTest.javaTEMPO +++ /dev/null @@ -1,48 +0,0 @@ -package fil.sr2.flopbox; - -import jakarta.ws.rs.client.Client; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.WebTarget; - -import org.glassfish.grizzly.http.server.HttpServer; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertEquals; - -public class MyResourceTest { - - private HttpServer server; - private WebTarget target; - - @Before - public void setUp() throws Exception { - // start the server - server = Main.startServer(); - // create the client - Client c = ClientBuilder.newClient(); - - // uncomment the following line if you want to enable - // support for JSON in the client (you also have to uncomment - // dependency on jersey-media-json module in pom.xml and Main.startServer()) - // -- - // c.configuration().enable(new org.glassfish.jersey.media.json.JsonJaxbFeature()); - - target = c.target(Main.BASE_URI); - } - - @After - public void tearDown() throws Exception { - server.stop(); - } - - /** - * Test to see that the message "Got it!" is sent in the response. - */ - @Test - public void testGetIt() { - String responseMsg = target.path("myresource").request().get(String.class); - assertEquals("Got it!", responseMsg); - } -}