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);
-    }
-}