diff --git a/README.md b/README.md
index b6ae3809083e5168bba722683530e584f322909b..bf30db1322963e80c0b4b1e1a43ae56817912ed9 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,31 @@
 
 ## Database
 
-This VOSpace implementation uses the database populated by the [VOSpace Transfer Service application](https://www.ict.inaf.it/gitlab/ia2/vospace-transfer-service). To avoid duplicating database definitions, DAO test classes load the database directly from the files of that repository. We assume that when running the tests the git repository exists and it is located in the same parent folder containing this repository. We could decide to create a dedicate common repository for sharing only the database structure and configuration files between the 2 projects.
+This VOSpace implementation uses the database populated by the [VOSpace Transfer Service application](https://www.ict.inaf.it/gitlab/ia2/vospace-transfer-service). The structure of the database is defined in a [separate repository](https://www.ict.inaf.it/gitlab/ia2/vospace-file-catalog). To avoid duplicating database definitions, DAO test classes load the database directly from the files of that repository. We assume that when running the tests the git repository exists and it is located in the same parent folder containing this repository.
 
 To reconfigure the path of that repository edit the property `init_database_scripts_path` in test.properties.
+
+## Loading fake users in MockMvc
+
+Test classes annotated with `@SpringBootTest` and `@AutoConfigureMockMvc` can be used to test REST controllers. Theoretically it should be possible configure a fake principal to each test request using the following notation:
+
+```java
+mockMvc.perform(post("/endpoint").principal(myFakeUser));
+```
+
+However it seems that the method is ignored if the principal is set using a custom servlet filter, like in our case (see `TokenFilter` registration defined in `VospaceApplication` class).
+
+To bypass the problem a fake `TokenFilter` has been defined in `TokenFilterConfig` test class. This filter returns some fake users based on the received fake token. If you need additional test users just add them in the `getFakeUser()` method.
+
+To use the fake filter add the following annotations to the test class:
+
+```java
+@ContextConfiguration(classes = {TokenFilterConfig.class})
+@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
+```
+
+Then add the fake token to the test request:
+
+```java
+mockMvc.perform(post("/endpoint").header("Authorization", "Bearer user1_token"));
+```
diff --git a/src/main/java/it/inaf/oats/vospace/TransferController.java b/src/main/java/it/inaf/oats/vospace/TransferController.java
index 271a998f85b8d1c0caafa97e2c91f1dde8d10a8e..0ceaf4219f7516b9b3c730fdd1556dfd69519bad 100644
--- a/src/main/java/it/inaf/oats/vospace/TransferController.java
+++ b/src/main/java/it/inaf/oats/vospace/TransferController.java
@@ -76,6 +76,8 @@ public class TransferController {
                 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
             }
 
+            jobService.setJobPhase(job, phase);
+
             return getJobRedirect(job.getJobId());
 
         }).orElse(ResponseEntity.notFound().build());
diff --git a/src/main/java/it/inaf/oats/vospace/UriService.java b/src/main/java/it/inaf/oats/vospace/UriService.java
index e9272eb5f27b03210ccaf112cd03e3af996077d2..4a2cb5382ea4e7f4bb2daded90bdb74353142796 100644
--- a/src/main/java/it/inaf/oats/vospace/UriService.java
+++ b/src/main/java/it/inaf/oats/vospace/UriService.java
@@ -1,11 +1,16 @@
 package it.inaf.oats.vospace;
 
+import it.inaf.ia2.aa.ServletRapClient;
+import it.inaf.ia2.aa.data.User;
+import it.inaf.ia2.rap.client.call.TokenExchangeRequest;
 import it.inaf.oats.vospace.persistence.NodeDAO;
 import java.util.ArrayList;
 import java.util.List;
+import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.uws.v1.ResultReference;
 import net.ivoa.xml.vospace.v2.Node;
+import net.ivoa.xml.vospace.v2.Property;
 import net.ivoa.xml.vospace.v2.Protocol;
 import net.ivoa.xml.vospace.v2.Transfer;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +29,12 @@ public class UriService {
     @Autowired
     private NodeDAO nodeDao;
 
+    @Autowired
+    private HttpServletRequest servletRequest;
+
+    @Autowired
+    private ServletRapClient rapClient;
+
     public void setTransferJobResult(JobSummary job) {
 
         List<ResultReference> results = new ArrayList<>();
@@ -39,11 +50,12 @@ public class UriService {
 
         Transfer transfer = getTransfer(job);
 
-        Protocol protocol = new Protocol();
-        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
-        protocol.setEndpoint(getEndpoint(job));
+        Protocol protocol = transfer.getProtocols().get(0);
 
-        transfer.getProtocols().add(protocol);
+        if (!"ivo://ivoa.net/vospace/core#httpget".equals(protocol.getUri())) {
+            throw new IllegalStateException("Unsupported protocol " + protocol.getUri());
+        }
+        protocol.setEndpoint(getEndpoint(job));
     }
 
     private String getEndpoint(JobSummary job) {
@@ -58,7 +70,40 @@ public class UriService {
         // TODO build the path according to node type
         //
         // TODO add token for authenticated access
-        return fileServiceUrl + relativePath + "?jobId=" + job.getJobId();
+        String endpoint = fileServiceUrl + relativePath + "?jobId=" + job.getJobId();
+
+        if (!"true".equals(getProperty(node, "publicread"))) {
+            endpoint += "&token=" + getEndpointToken(fileServiceUrl + relativePath);
+        }
+
+        return endpoint;
+    }
+
+    private String getEndpointToken(String endpoint) {
+
+        String token = ((User) servletRequest.getUserPrincipal()).getAccessToken();
+
+        if (token == null) {
+            // TODO: use PermissionDenied VoSpaceException
+            throw new IllegalStateException("Token is null");
+        }
+
+        TokenExchangeRequest exchangeRequest = new TokenExchangeRequest()
+                .setSubjectToken(token)
+                .setResource(endpoint);
+
+        // TODO: add audience and scope
+        return rapClient.exchangeToken(exchangeRequest, servletRequest);
+    }
+
+    private String getProperty(Node node, String propertyName) {
+
+        for (Property property : node.getProperties()) {
+            if (property.getUri().equals("ivo://ivoa.net/vospace/core#".concat(propertyName))) {
+                return property.getValue();
+            }
+        }
+        return null;
     }
 
     private Transfer getTransfer(JobSummary job) {
diff --git a/src/main/java/it/inaf/oats/vospace/VospaceApplication.java b/src/main/java/it/inaf/oats/vospace/VospaceApplication.java
index f815559a02f3ba6ef794304644115ca00e0924d9..bd776e7bd5edd7f2aef27e82e5e2cf8c74471532 100644
--- a/src/main/java/it/inaf/oats/vospace/VospaceApplication.java
+++ b/src/main/java/it/inaf/oats/vospace/VospaceApplication.java
@@ -1,5 +1,7 @@
 package it.inaf.oats.vospace;
 
+import it.inaf.ia2.aa.ServiceLocator;
+import it.inaf.ia2.aa.ServletRapClient;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -20,4 +22,9 @@ public class VospaceApplication {
         registration.addUrlPatterns("/*");
         return registration;
     }
+
+    @Bean
+    public ServletRapClient servletRapClient() {
+        return (ServletRapClient) ServiceLocator.getInstance().getRapClient();
+    }
 }
diff --git a/src/test/java/it/inaf/oats/vospace/TokenFilterConfig.java b/src/test/java/it/inaf/oats/vospace/TokenFilterConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6eb1094c10ed5ba149d45cee0def734e104f3ac
--- /dev/null
+++ b/src/test/java/it/inaf/oats/vospace/TokenFilterConfig.java
@@ -0,0 +1,98 @@
+package it.inaf.oats.vospace;
+
+import it.inaf.ia2.aa.TokenFilter;
+import it.inaf.ia2.aa.data.User;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+@TestConfiguration
+public class TokenFilterConfig {
+
+    @Bean
+    @Primary
+    public FilterRegistrationBean tokenFilterRegistration() {
+        FilterRegistrationBean registration = new FilterRegistrationBean();
+        registration.setFilter(new FakeTokenFilter());
+        registration.addUrlPatterns("/*");
+        return registration;
+    }
+
+    private static class FakeTokenFilter extends TokenFilter {
+
+        @Override
+        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+
+            HttpServletRequest request = (HttpServletRequest) req;
+            HttpServletResponse response = (HttpServletResponse) res;
+
+            String authHeader = request.getHeader("Authorization");
+
+            if (authHeader != null) {
+                if (authHeader.startsWith("Bearer ")) {
+                    String token = authHeader.substring("Bearer ".length());
+                    HttpServletRequestWrapper requestWithPrincipal = new RequestWithPrincipal(request, getFakeUser(token));
+                    chain.doFilter(requestWithPrincipal, response);
+                    return;
+                }
+            }
+
+            chain.doFilter(getAnonymousServletRequest(request), response);
+        }
+
+        private User getFakeUser(String token) {
+
+            User user = new User();
+
+            switch (token) {
+                case "user1_token":
+                    user.setUserId("user1").setUserLabel("User1");
+                    break;
+                case "user2_token":
+                    user.setUserId("user2").setUserLabel("User2").setGroups(Arrays.asList("group1", "group2"));
+                    break;
+                default:
+                    throw new IllegalArgumentException("Fake user not configured for token " + token);
+            }
+
+            user.setAccessToken(token);
+
+            return user;
+        }
+
+        private static HttpServletRequestWrapper getAnonymousServletRequest(HttpServletRequest request) {
+            User anonymousUser = new User()
+                    .setUserId("anonymous")
+                    .setUserLabel("Anonymous")
+                    .setGroups(new ArrayList<>());
+            return new RequestWithPrincipal(request, anonymousUser);
+        }
+
+        private static class RequestWithPrincipal extends HttpServletRequestWrapper {
+
+            private final User user;
+
+            public RequestWithPrincipal(HttpServletRequest request, User user) {
+                super(request);
+                this.user = user;
+            }
+
+            @Override
+            public Principal getUserPrincipal() {
+                return user;
+            }
+        }
+    }
+}
diff --git a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
index d87e1f64702ff1c2e0c8f5c8c0ecd8d8b1a4c4f7..5b2e92b57852dbeb31a29d6dda1c3c32a5bdc51b 100644
--- a/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
+++ b/src/test/java/it/inaf/oats/vospace/TransferControllerTest.java
@@ -1,11 +1,24 @@
 package it.inaf.oats.vospace;
 
+import it.inaf.ia2.aa.data.User;
 import static it.inaf.oats.vospace.VOSpaceXmlTestUtil.loadDocument;
 import it.inaf.oats.vospace.persistence.JobDAO;
+import it.inaf.oats.vospace.persistence.NodeDAO;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.Optional;
+import net.ivoa.xml.uws.v1.ExecutionPhase;
 import net.ivoa.xml.uws.v1.JobSummary;
+import net.ivoa.xml.vospace.v2.DataNode;
+import net.ivoa.xml.vospace.v2.Node;
+import net.ivoa.xml.vospace.v2.Property;
+import net.ivoa.xml.vospace.v2.Protocol;
+import net.ivoa.xml.vospace.v2.Transfer;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.matchesPattern;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 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;
@@ -15,28 +28,157 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.http.MediaType;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.TestPropertySource;
 import org.springframework.test.web.servlet.MockMvc;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 import org.w3c.dom.Document;
 
 @SpringBootTest
 @AutoConfigureMockMvc
+@ContextConfiguration(classes = {TokenFilterConfig.class})
+@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
 public class TransferControllerTest {
 
     @MockBean
-    private JobDAO dao;
+    private JobDAO jobDao;
+
+    @MockBean
+    private NodeDAO nodeDao;
+
+    @MockBean
+    private TapeService tapeService;
 
     @Autowired
     private MockMvc mockMvc;
-    
+
+    @Test
+    public void testPullFromVoSpaceAsync() throws Exception {
+
+        Node node = mockPublicDataNode();
+        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
+
+        String requestBody = getResourceFileContent("pullFromVoSpace.xml");
+
+        String redirect = mockMvc.perform(post("/transfers?PHASE=RUN")
+                .content(requestBody)
+                .contentType(MediaType.APPLICATION_XML)
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().is3xxRedirection())
+                .andReturn().getResponse().getHeader("Location");
+
+        assertThat(redirect, matchesPattern("^/transfers/.*"));
+    }
+
+    @Test
+    public void testPullFromVoSpaceSync() throws Exception {
+
+        Node node = mockPublicDataNode();
+        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
+
+        String requestBody = getResourceFileContent("pullFromVoSpace.xml");
+
+        String redirect = mockMvc.perform(post("/synctrans")
+                .content(requestBody)
+                .contentType(MediaType.APPLICATION_XML)
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().is3xxRedirection())
+                .andReturn().getResponse().getHeader("Location");
+
+        assertThat(redirect, matchesPattern("^/transfers/.*/results/transferDetails"));
+    }
+
+    @Test
+    public void testPullToVoSpace() throws Exception {
+
+        String requestBody = getResourceFileContent("pullToVoSpace.xml");
+
+        String redirect = mockMvc.perform(post("/transfers?PHASE=RUN")
+                .content(requestBody)
+                .contentType(MediaType.APPLICATION_XML)
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().is3xxRedirection())
+                .andReturn().getResponse().getHeader("Location");
+
+        assertThat(redirect, matchesPattern("^/transfers/.*"));
+
+        verify(tapeService, times(1)).startJob(any());
+    }
+
+    @Test
+    public void testSetJobPhase() throws Exception {
+
+        Node node = mockPublicDataNode();
+        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));
+
+        JobSummary job = getFakePendingJob();
+        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
+
+        User user = new User();
+        user.setUserId("ownerId");
+
+        String redirect = mockMvc.perform(post("/transfers/123/phase")
+                .header("Authorization", "Bearer user1_token")
+                .param("PHASE", "RUN")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().is3xxRedirection())
+                .andReturn().getResponse().getHeader("Location");
+
+        verify(jobDao, times(1)).updateJob(any());
+
+        assertThat(redirect, matchesPattern("^/transfers/.*"));
+    }
+
+    @Test
+    public void testGetTransferDetails() throws Exception {
+
+        JobSummary job = getFakePendingJob();
+        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
+
+        mockMvc.perform(get("/transfers/123/results/transferDetails")
+                .header("Authorization", "Bearer user1_token")
+                .accept(MediaType.APPLICATION_XML))
+                .andDo(print())
+                .andExpect(status().isOk());
+    }
+
+    @Test
+    public void testGetJobPhase() throws Exception {
+
+        JobSummary job = getFakePendingJob();
+        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
+
+        String phase = mockMvc.perform(get("/transfers/123/phase")
+                .header("Authorization", "Bearer user1_token"))
+                .andDo(print())
+                .andExpect(status().isOk())
+                .andReturn().getResponse().getContentAsString();
+
+        assertEquals("PENDING", phase);
+    }
+
+    private Node mockPublicDataNode() {
+        Node node = new DataNode();
+        Property property = new Property();
+        property.setUri("ivo://ivoa.net/vospace/core#publicread");
+        property.setValue("true");
+        node.getProperties().add(property);
+        return node;
+    }
+
     @Test
     public void testGetJob() throws Exception {
 
         JobSummary job = new JobSummary();
 
-        when(dao.getJob(eq("123"))).thenReturn(Optional.of(job));
+        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
 
         String xml = mockMvc.perform(get("/transfers/123")
                 .accept(MediaType.APPLICATION_XML))
@@ -47,6 +189,31 @@ public class TransferControllerTest {
         Document doc = loadDocument(xml);
         assertEquals("uws:job", doc.getDocumentElement().getNodeName());
 
-        verify(dao, times(1)).getJob(eq("123"));
+        verify(jobDao, times(1)).getJob(eq("123"));
+    }
+
+    private JobSummary getFakePendingJob() {
+        JobSummary job = new JobSummary();
+        job.setPhase(ExecutionPhase.PENDING);
+        job.setOwnerId("user1");
+
+        Transfer transfer = new Transfer();
+        transfer.setDirection("pullFromVoSpace");
+        transfer.setTarget("vos://example.com!vospace/mynode");
+        Protocol protocol = new Protocol();
+        protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
+        transfer.getProtocols().add(protocol);
+
+        JobSummary.JobInfo jobInfo = new JobSummary.JobInfo();
+        jobInfo.getAny().add(transfer);
+
+        job.setJobInfo(jobInfo);
+        return job;
+    }
+
+    protected static String getResourceFileContent(String fileName) throws Exception {
+        try ( InputStream in = TransferControllerTest.class.getClassLoader().getResourceAsStream(fileName)) {
+            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+        }
     }
 }
diff --git a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
index 7112eac8c679b40cec8d6d5b6f4a4110215c86c4..b50eda77988ef4ed2c4b466e080c5637257393a2 100644
--- a/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
+++ b/src/test/java/it/inaf/oats/vospace/UriServiceTest.java
@@ -1,19 +1,29 @@
 package it.inaf.oats.vospace;
 
+import it.inaf.ia2.aa.ServletRapClient;
+import it.inaf.ia2.aa.data.User;
 import it.inaf.oats.vospace.persistence.NodeDAO;
 import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
 import net.ivoa.xml.uws.v1.JobSummary;
 import net.ivoa.xml.vospace.v2.DataNode;
 import net.ivoa.xml.vospace.v2.Node;
+import net.ivoa.xml.vospace.v2.Property;
 import net.ivoa.xml.vospace.v2.Transfer;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import org.junit.jupiter.api.Test;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
 import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
 import org.springframework.test.context.TestPropertySource;
 
 @SpringBootTest
@@ -24,16 +34,71 @@ public class UriServiceTest {
     @MockBean
     private NodeDAO dao;
 
+    @MockBean
+    private ServletRapClient rapClient;
+
+    @Autowired
+    private HttpServletRequest servletRequest;
+
     @Autowired
-    private UriService transferService;
+    private UriService uriService;
+
+    @TestConfiguration
+    public static class TestConfig {
+
+        /**
+         * Necessary because MockBean doesn't work with HttpServletRequest.
+         */
+        @Bean
+        @Primary
+        public HttpServletRequest servletRequest() {
+            return mock(HttpServletRequest.class);
+        }
+    }
+
+    @Test
+    public void testPublicUrl() {
+
+        Node node = new DataNode();
+        Property property = new Property();
+        property.setUri("ivo://ivoa.net/vospace/core#publicread");
+        property.setValue("true");
+        node.getProperties().add(property);
+
+        when(dao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
+
+        JobSummary job = getJob();
+        uriService.setTransferJobResult(job);
+
+        assertEquals("http://file-service/mydata1?jobId=job-id", job.getResults().get(0).getHref());
+    }
 
     @Test
-    public void testSimpleUrl() {
+    public void testPrivateUrl() {
 
         Node node = new DataNode();
 
         when(dao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
 
+        User user = mock(User.class);
+        when(user.getAccessToken()).thenReturn("<token>");
+
+        when(servletRequest.getUserPrincipal()).thenReturn(user);
+
+        when(rapClient.exchangeToken(argThat(req -> {
+            assertEquals("<token>", req.getSubjectToken());
+            assertEquals("http://file-service/mydata1", req.getResource());
+            return true;
+        }), any())).thenReturn("<new-token>");
+
+        JobSummary job = getJob();
+        uriService.setTransferJobResult(job);
+
+        assertEquals("http://file-service/mydata1?jobId=job-id&token=<new-token>", job.getResults().get(0).getHref());
+    }
+
+    private JobSummary getJob() {
+
         Transfer transfer = new Transfer();
         transfer.setTarget("vos://example.com!vospace/mydata1");
 
@@ -45,8 +110,6 @@ public class UriServiceTest {
 
         job.setJobInfo(jobInfo);
 
-        transferService.setTransferJobResult(job);
-
-        assertEquals("http://file-service/mydata1?jobId=job-id", job.getResults().get(0).getHref());
+        return job;
     }
 }
diff --git a/src/test/resources/pullFromVoSpace.xml b/src/test/resources/pullFromVoSpace.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a2c0ad756825b528a800c995d100bc78b8fb31e7
--- /dev/null
+++ b/src/test/resources/pullFromVoSpace.xml
@@ -0,0 +1,5 @@
+<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" version="2.1">
+    <vos:target>vos://example.com!vospace/mynode</vos:target>
+    <vos:direction>pullFromVoSpace</vos:direction>
+    <vos:protocol uri="ivo://ivoa.net/vospace/core#httpget" />
+</vos:transfer>
\ No newline at end of file
diff --git a/src/test/resources/pullToVoSpace.xml b/src/test/resources/pullToVoSpace.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8673c57e68c120f52a1b92b7988b3835cb3c4d51
--- /dev/null
+++ b/src/test/resources/pullToVoSpace.xml
@@ -0,0 +1,5 @@
+<vos:transfer xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" version="2.1">
+    <vos:target>vos://example.com!vospace/mynode</vos:target>
+    <vos:direction>pullToVoSpace</vos:direction>
+    <vos:protocol uri="ivo://ivoa.net/vospace/core#httpget" />
+</vos:transfer>
\ No newline at end of file