diff --git a/src/main/java/it/inaf/ia2/transfer/auth/GmsClient.java b/src/main/java/it/inaf/ia2/transfer/auth/GmsClient.java
index 781311aa100dec744c65e5ec5f4be29c977ceda0..8b0adc3a16883bcb77577f5105a617d20b02c795 100644
--- a/src/main/java/it/inaf/ia2/transfer/auth/GmsClient.java
+++ b/src/main/java/it/inaf/ia2/transfer/auth/GmsClient.java
@@ -31,8 +31,8 @@ public class GmsClient {
     private final RestTemplate restTemplate;
 
     @Autowired
-    public GmsClient() {
-        restTemplate = new RestTemplate();
+    public GmsClient(RestTemplate restTemplate) {
+        this.restTemplate = restTemplate;
     }
 
     @Cacheable("gms_cache")
diff --git a/src/test/java/it/inaf/ia2/transfer/auth/GmsClientTest.java b/src/test/java/it/inaf/ia2/transfer/auth/GmsClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..32f57f3a8c7ec0d4728c944054798499aee4b1b7
--- /dev/null
+++ b/src/test/java/it/inaf/ia2/transfer/auth/GmsClientTest.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of vospace-file-service
+ * Copyright (C) 2021 Istituto Nazionale di Astrofisica
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package it.inaf.ia2.transfer.auth;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+@ExtendWith(MockitoExtension.class)
+public class GmsClientTest {
+
+    @Mock
+    private RestTemplate restTemplate;
+
+    @InjectMocks
+    private GmsClient gmsClient;
+
+    @Test
+    public void testIsMemberOf() {
+        mockGmsResponse("group1\n");
+        assertTrue(gmsClient.isMemberOf("<token>", "group1"));
+    }
+
+    @Test
+    public void testIsNotMemberOf() {
+        mockGmsResponse("");
+        assertFalse(gmsClient.isMemberOf("<token>", "group1"));
+    }
+
+    private void mockGmsResponse(String body) {
+
+        ResponseEntity<String> mockedResponse = mock(ResponseEntity.class);
+        when(mockedResponse.getBody()).thenReturn(body);
+
+        when(restTemplate.exchange(any(String.class), eq(HttpMethod.GET), any(HttpEntity.class), eq(String.class))).thenReturn(mockedResponse);
+    }
+}
diff --git a/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java
index f15b20d23f0e93f269bb292d589e777051089aab..5214787b24d049e19e1c92fb1118d8195060fa16 100644
--- a/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java
+++ b/src/test/java/it/inaf/ia2/transfer/controller/GetFileControllerTest.java
@@ -8,18 +8,23 @@ package it.inaf.ia2.transfer.controller;
 import it.inaf.ia2.transfer.persistence.model.FileInfo;
 import it.inaf.ia2.aa.jwt.TokenParser;
 import it.inaf.ia2.transfer.auth.GmsClient;
+import it.inaf.ia2.transfer.exception.JobException;
 import it.inaf.ia2.transfer.persistence.FileDAO;
+import it.inaf.ia2.transfer.persistence.JobDAO;
 import java.io.File;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import net.ivoa.xml.uws.v1.ExecutionPhase;
 import org.assertj.core.util.Files;
 import org.junit.jupiter.api.AfterEach;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +49,9 @@ public class GetFileControllerTest {
     @MockBean
     private FileDAO fileDao;
 
+    @MockBean
+    private JobDAO jobDAO;
+
     @Autowired
     private MockMvc mockMvc;
 
@@ -135,23 +143,6 @@ public class GetFileControllerTest {
         verify(gmsClient).isMemberOf(eq("<token>"), eq("group1"));
     }
 
-    private void prepareMocksForPrivateFile() {
-
-        Map<String, Object> claims = new HashMap<>();
-        claims.put("sub", "123");
-
-        when(tokenParser.getClaims(any())).thenReturn(claims);
-
-        when(gmsClient.isMemberOf(any(), any())).thenReturn(true);
-
-        FileInfo fileInfo = new FileInfo();
-        fileInfo.setOsPath(tempFile.getAbsolutePath());
-        fileInfo.setVirtualPath("/path/to/myfile");
-        fileInfo.setGroupRead(Collections.singletonList("group1"));
-
-        when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
-    }
-
     @Test
     public void getPrivateFileByOwnerId() throws Exception {
 
@@ -185,4 +176,80 @@ public class GetFileControllerTest {
                 .andDo(print())
                 .andExpect(status().isForbidden());
     }
+
+    @Test
+    public void testGetFileWithJobId() throws Exception {
+
+        FileInfo fileInfo = new FileInfo();
+        fileInfo.setOsPath(tempFile.getAbsolutePath());
+        fileInfo.setVirtualPath("/path/to/myfile");
+        fileInfo.setPublic(true);
+
+        when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
+
+        when(jobDAO.isJobExisting(eq("abcdef"))).thenReturn(true);
+
+        mockMvc.perform(get("/path/to/myfile")
+                .param("jobId", "abcdef"))
+                .andDo(print())
+                .andExpect(status().isOk());
+
+        verify(jobDAO, times(1)).updateJobPhase(eq(ExecutionPhase.COMPLETED), eq("abcdef"));
+    }
+
+    @Test
+    public void testGetFileWithInvalidJobId() throws Exception {
+
+        mockMvc.perform(get("/path/to/myfile")
+                .param("jobId", "abcdef"))
+                .andDo(print())
+                .andExpect(status().isBadRequest());
+    }
+
+    @Test
+    public void testGetFileNotReadable() throws Exception {
+
+        File unreadableFile = Files.newTemporaryFile();
+
+        try {
+
+            unreadableFile.setReadable(false);
+
+            FileInfo fileInfo = new FileInfo();
+            fileInfo.setOsPath(unreadableFile.getAbsolutePath());
+            fileInfo.setVirtualPath("/path/to/myfile");
+            fileInfo.setPublic(true);
+
+            when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
+
+            Exception ex = mockMvc.perform(get("/path/to/myfile"))
+                    .andDo(print())
+                    .andReturn().getResolvedException();
+
+            assertTrue(ex instanceof JobException);
+            JobException jobEx = (JobException) ex;
+            assertTrue(jobEx.getErrorDetail().contains("not readable"), jobEx.getErrorDetail());
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            assertTrue(unreadableFile.delete());
+        }
+    }
+
+    private void prepareMocksForPrivateFile() {
+
+        Map<String, Object> claims = new HashMap<>();
+        claims.put("sub", "123");
+
+        when(tokenParser.getClaims(any())).thenReturn(claims);
+
+        when(gmsClient.isMemberOf(any(), any())).thenReturn(true);
+
+        FileInfo fileInfo = new FileInfo();
+        fileInfo.setOsPath(tempFile.getAbsolutePath());
+        fileInfo.setVirtualPath("/path/to/myfile");
+        fileInfo.setGroupRead(Collections.singletonList("group1"));
+
+        when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
+    }
 }
diff --git a/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java b/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java
index 90531f8cb8d536a4c146aef9765663eec7764af0..9fe0554eb418098a137e90154ea8c54c0fd0557a 100644
--- a/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java
+++ b/src/test/java/it/inaf/ia2/transfer/controller/PutFileControllerTest.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -32,9 +33,11 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockMultipartFile;
 import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.request.RequestPostProcessor;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
 import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import org.springframework.util.FileSystemUtils;
@@ -94,7 +97,7 @@ public class PutFileControllerTest {
         putGenericFileWithNameConflict("test", "test-1", "test-2");
     }
 
-    public void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception {
+    private void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception {
 
         createBaseFileInfo(name1);
 
@@ -172,6 +175,58 @@ public class PutFileControllerTest {
         assertTrue(file.delete());
     }
 
+    @Test
+    public void testPutFileWithoutNodeInDatabase() throws Exception {
+
+        MockMultipartFile fakeFile = new MockMultipartFile("file", "foo.txt", "text/plain", "content".getBytes());
+
+        mockMvc.perform(putMultipart("/path/to/foo.txt")
+                .file(fakeFile))
+                .andDo(print())
+                .andExpect(status().isNotFound());
+    }
+
+    @Test
+    public void testPutWithInputStream() throws Exception {
+
+        createBaseFileInfo();
+
+        mockMvc.perform(put("/path/to/stream.txt"))
+                .andDo(print())
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void testJobError() throws Exception {
+
+        createBaseFileInfo();
+
+        when(jobDAO.isJobExisting(eq("abcdef"))).thenReturn(true);
+
+        MockHttpServletRequestBuilder errorBuilder = put("/path/to/error");
+        errorBuilder.with(new RequestPostProcessor() {
+            @Override
+            public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
+                MockHttpServletRequest spyRequest = spy(request);
+                when(spyRequest.getInputStream()).thenThrow(new RuntimeException());
+                return spyRequest;
+            }
+        });
+
+        try {
+            mockMvc.perform(errorBuilder
+                    .param("jobId", "abcdef"));
+        } catch (Exception ex) {
+        }
+
+        verify(jobDAO, times(1)).setJobError(eq("abcdef"), any());
+    }
+
+    private FileInfo createBaseFileInfo() {
+        String randomFileName = UUID.randomUUID().toString();
+        return createBaseFileInfo(randomFileName);
+    }
+
     private FileInfo createBaseFileInfo(String fileName) {
         FileInfo fileInfo = new FileInfo();
         fileInfo.setOsPath(getTestFilePath(fileName));
@@ -183,7 +238,7 @@ public class PutFileControllerTest {
     }
 
     private String getTestFilePath(String fileName) {
-        return temporaryDirectory.toPath().resolve(fileName).toFile().getAbsolutePath();
+        return temporaryDirectory.toPath().resolve("subdir").resolve(fileName).toFile().getAbsolutePath();
     }
 
     private MockMultipartHttpServletRequestBuilder putMultipart(String uri) {