Skip to content
Snippets Groups Projects
Commit 3d0c8655 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Handled URL encoded node URI in VOSpace REST responses and added some tests

parent d60374fa
No related branches found
No related tags found
No related merge requests found
Pipeline #2009 passed
...@@ -106,6 +106,9 @@ ...@@ -106,6 +106,9 @@
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version> <version>2.22.2</version>
<configuration>
<trimStackTrace>false</trimStackTrace>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.jacoco</groupId> <groupId>org.jacoco</groupId>
......
...@@ -142,7 +142,7 @@ public class VOSpaceClient { ...@@ -142,7 +142,7 @@ public class VOSpaceClient {
String path = node.getUri().substring(("vos://" + authority).length()); String path = node.getUri().substring(("vos://" + authority).length());
HttpRequest request = getRequest("/nodes" + urlEncodePath(path)) HttpRequest request = getRequest("/nodes" + path)
.header("Accept", useJson ? "application/json" : "text/xml") .header("Accept", useJson ? "application/json" : "text/xml")
.header("Content-Type", useJson ? "application/json" : "text/xml") .header("Content-Type", useJson ? "application/json" : "text/xml")
.PUT(HttpRequest.BodyPublishers.ofString(marshal(node))) .PUT(HttpRequest.BodyPublishers.ofString(marshal(node)))
...@@ -166,7 +166,7 @@ public class VOSpaceClient { ...@@ -166,7 +166,7 @@ public class VOSpaceClient {
String path = node.getUri().substring(("vos://" + authority).length()); String path = node.getUri().substring(("vos://" + authority).length());
HttpRequest request = getRequest("/nodes" + urlEncodePath(path) + "?recursive=" + recursive) HttpRequest request = getRequest("/nodes" + path + "?recursive=" + recursive)
.header("Accept", useJson ? "application/json" : "text/xml") .header("Accept", useJson ? "application/json" : "text/xml")
.header("Content-Type", useJson ? "application/json" : "text/xml") .header("Content-Type", useJson ? "application/json" : "text/xml")
.POST(HttpRequest.BodyPublishers.ofString(marshal(node))) .POST(HttpRequest.BodyPublishers.ofString(marshal(node)))
......
...@@ -13,6 +13,7 @@ import it.inaf.ia2.vospace.ui.exception.VOSpaceException; ...@@ -13,6 +13,7 @@ import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import it.inaf.ia2.vospace.ui.service.MainNodesHtmlGenerator; import it.inaf.ia2.vospace.ui.service.MainNodesHtmlGenerator;
import it.inaf.ia2.vospace.ui.service.MoveNodeModalHtmlGenerator; import it.inaf.ia2.vospace.ui.service.MoveNodeModalHtmlGenerator;
import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.NodeUtils;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -79,6 +80,8 @@ public class NodesController extends BaseController { ...@@ -79,6 +80,8 @@ public class NodesController extends BaseController {
@GetMapping(value = "/nodesForMove", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/nodesForMove", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ListNodeData> listNodesForMoveModal(@RequestParam("path") String path, @RequestParam("nodeToMove") String nodeToMove, User principal) throws Exception { public ResponseEntity<ListNodeData> listNodesForMoveModal(@RequestParam("path") String path, @RequestParam("nodeToMove") String nodeToMove, User principal) throws Exception {
LOG.debug("listNodes called for path {}", path);
ListNodeData listNodeData = new ListNodeData(); ListNodeData listNodeData = new ListNodeData();
Node node = client.getNode(path); Node node = client.getNode(path);
...@@ -99,7 +102,7 @@ public class NodesController extends BaseController { ...@@ -99,7 +102,7 @@ public class NodesController extends BaseController {
Transfer transfer = new Transfer(); Transfer transfer = new Transfer();
transfer.setDirection("pullFromVoSpace"); transfer.setDirection("pullFromVoSpace");
transfer.setTarget(Arrays.asList("vos://" + authority + path)); transfer.setTarget(Arrays.asList("vos://" + authority + urlEncodePath(path)));
Protocol protocol = new Protocol(); Protocol protocol = new Protocol();
protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); protocol.setUri("ivo://ivoa.net/vospace/core#httpget");
...@@ -123,7 +126,7 @@ public class NodesController extends BaseController { ...@@ -123,7 +126,7 @@ public class NodesController extends BaseController {
LOG.debug("newFolder called for path {}/{}", parentPath, name); LOG.debug("newFolder called for path {}/{}", parentPath, name);
ContainerNode node = new ContainerNode(); ContainerNode node = new ContainerNode();
node.setUri("vos://" + authority + parentPath + "/" + name); node.setUri("vos://" + authority + urlEncodePath(parentPath + "/" + name));
Property creator = new Property(); Property creator = new Property();
creator.setUri("ivo://ivoa.net/vospace/core#creator"); creator.setUri("ivo://ivoa.net/vospace/core#creator");
...@@ -162,8 +165,8 @@ public class NodesController extends BaseController { ...@@ -162,8 +165,8 @@ public class NodesController extends BaseController {
@PostMapping(value = "/move") @PostMapping(value = "/move")
public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) { public ResponseEntity<Job> moveNode(@RequestBody Map<String, Object> params) {
String target = getRequiredParam(params, "target"); String target = urlEncodePath(getRequiredParam(params, "target"));
String direction = getRequiredParam(params, "direction"); String direction = urlEncodePath(getRequiredParam(params, "direction"));
Transfer transfer = new Transfer(); Transfer transfer = new Transfer();
transfer.setTarget(Arrays.asList("vos://" + authority + target)); transfer.setTarget(Arrays.asList("vos://" + authority + target));
......
...@@ -8,6 +8,7 @@ package it.inaf.ia2.vospace.ui.controller; ...@@ -8,6 +8,7 @@ package it.inaf.ia2.vospace.ui.controller;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.UploadFilesData; import it.inaf.ia2.vospace.ui.data.UploadFilesData;
import it.inaf.ia2.vospace.ui.exception.PermissionDeniedException; import it.inaf.ia2.vospace.ui.exception.PermissionDeniedException;
import static it.inaf.oats.vospace.datamodel.NodeUtils.urlEncodePath;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
...@@ -64,11 +65,15 @@ public class UploadController extends BaseController { ...@@ -64,11 +65,15 @@ public class UploadController extends BaseController {
public CompletableFuture<String> prepareForDownload(String parentPath, String fileName) { public CompletableFuture<String> prepareForDownload(String parentPath, String fileName) {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
String nodeUri = "vos://" + authority + parentPath;
if (!nodeUri.endsWith("/")) { String path = parentPath;
nodeUri += "/";
if (!path.endsWith("/")) {
path += "/";
} }
nodeUri += fileName; path += fileName;
String nodeUri = "vos://" + authority + urlEncodePath(path);
createDataNode(nodeUri, getUser().getName()); createDataNode(nodeUri, getUser().getName());
......
...@@ -9,12 +9,14 @@ import it.inaf.ia2.aa.data.User; ...@@ -9,12 +9,14 @@ import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException; import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils; import it.inaf.oats.vospace.datamodel.NodeUtils;
import java.util.List; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property; import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.View;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -41,7 +43,7 @@ public class NodeInfo { ...@@ -41,7 +43,7 @@ public class NodeInfo {
public NodeInfo(Node node, User user, String authority) { public NodeInfo(Node node, User user, String authority) {
this.authority = authority; this.authority = authority;
this.path = getPath(node); this.path = getPath(node);
this.name = path.substring(path.lastIndexOf("/") + 1); this.name = URLDecoder.decode(path.substring(path.lastIndexOf("/") + 1), StandardCharsets.UTF_8);
this.size = getSize(node); this.size = getSize(node);
this.type = node.getType(); this.type = node.getType();
this.creator = getCreator(node); this.creator = getCreator(node);
...@@ -65,7 +67,10 @@ public class NodeInfo { ...@@ -65,7 +67,10 @@ public class NodeInfo {
throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri); throw new VOSpaceException("Node authority is different from configured one! Configured is " + authority + ", but node URI is " + uri);
} }
return uri.substring(prefix.length()); // returns decoded path
return String.join("/", Arrays.stream(uri.substring(prefix.length()).split("/"))
.map(p -> URLDecoder.decode(p, StandardCharsets.UTF_8))
.collect(Collectors.toList()));
} }
private String getCreator(Node node) { private String getCreator(Node node) {
......
...@@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream; ...@@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.net.URISyntaxException;
import java.net.http.HttpClient; import java.net.http.HttpClient;
import java.net.http.HttpHeaders; import java.net.http.HttpHeaders;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
...@@ -24,6 +25,8 @@ import net.ivoa.xml.vospace.v2.Protocol; ...@@ -24,6 +25,8 @@ import net.ivoa.xml.vospace.v2.Protocol;
import net.ivoa.xml.vospace.v2.Transfer; import net.ivoa.xml.vospace.v2.Transfer;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
...@@ -90,17 +93,17 @@ public class VOSpaceClientTest { ...@@ -90,17 +93,17 @@ public class VOSpaceClientTest {
} }
@Test @Test
public void testCreateNodeBadName() { public void testCreateNodeBadUri() {
ContainerNode newNode = new ContainerNode(); ContainerNode newNode = new ContainerNode();
newNode.setUri("vos://ia2.inaf.it!vospace/mynode/File with spaces.and.dots.pdf"); newNode.setUri("vos://ia2.inaf.it!vospace/mynode/spaces not encoded");
ReflectionTestUtils.setField(voSpaceClient, "useJson", false);
CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("node-response.xml"));
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
try {
voSpaceClient.createNode(newNode); voSpaceClient.createNode(newNode);
fail("Exception was expected");
} catch (IllegalArgumentException ex) {
assertTrue(ex.getCause() instanceof URISyntaxException);
}
} }
@Test @Test
...@@ -196,6 +199,41 @@ public class VOSpaceClientTest { ...@@ -196,6 +199,41 @@ public class VOSpaceClientTest {
assertEquals(ExecutionPhase.COMPLETED, voSpaceClient.getJobPhase("job_id")); assertEquals(ExecutionPhase.COMPLETED, voSpaceClient.getJobPhase("job_id"));
} }
@Test
public void testSetNode() {
ContainerNode node = new ContainerNode();
node.setUri("vos://ia2.inaf.it!vospace/mynode");
CompletableFuture response = getMockedStreamResponseFuture(200, getResourceFileContent("node-response.xml"));
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
voSpaceClient.setNode(node, true);
}
@Test
public void testSetNodeBadUri() {
ContainerNode node = new ContainerNode();
node.setUri("vos://ia2.inaf.it!vospace/not urlencoded");
try {
voSpaceClient.setNode(node, true);
fail("Exception was expected");
} catch (IllegalArgumentException ex) {
assertTrue(ex.getCause() instanceof URISyntaxException);
}
}
@Test
public void testDeleteNode() {
CompletableFuture response = getMockedStreamResponseFuture(200, "");
when(mockedHttpClient.sendAsync(any(), any())).thenReturn(response);
voSpaceClient.deleteNode("/not urlencoded");
}
protected static String getResourceFileContent(String fileName) { protected static String getResourceFileContent(String fileName) {
try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) { try ( InputStream in = VOSpaceClientTest.class.getClassLoader().getResourceAsStream(fileName)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8); return new String(in.readAllBytes(), StandardCharsets.UTF_8);
......
...@@ -6,12 +6,14 @@ ...@@ -6,12 +6,14 @@
package it.inaf.ia2.vospace.ui.controller; package it.inaf.ia2.vospace.ui.controller;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.vospace.ui.client.VOSpaceClient; import it.inaf.ia2.vospace.ui.client.VOSpaceClient;
import it.inaf.ia2.vospace.ui.data.Job; import it.inaf.ia2.vospace.ui.data.Job;
import it.inaf.ia2.vospace.ui.exception.VOSpaceException; import it.inaf.ia2.vospace.ui.exception.VOSpaceException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.servlet.http.HttpSession;
import net.ivoa.xml.uws.v1.ExecutionPhase; import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary; import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
...@@ -24,6 +26,7 @@ import org.junit.jupiter.api.Test; ...@@ -24,6 +26,7 @@ import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
...@@ -33,6 +36,7 @@ import org.springframework.boot.test.context.SpringBootTest; ...@@ -33,6 +36,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
...@@ -50,6 +54,9 @@ public class NodesControllerTest { ...@@ -50,6 +54,9 @@ public class NodesControllerTest {
@MockBean @MockBean
private VOSpaceClient client; private VOSpaceClient client;
@Autowired
private NodesController nodesController;
@Autowired @Autowired
private MockMvc mockMvc; private MockMvc mockMvc;
...@@ -198,6 +205,33 @@ public class NodesControllerTest { ...@@ -198,6 +205,33 @@ public class NodesControllerTest {
} }
} }
@Test
public void testDirectDownload() throws Exception {
when(client.getFileServiceEndpoint(any())).thenReturn("http://redirect");
mockMvc.perform(get("/download/myfile"))
.andExpect(status().is3xxRedirection());
verify(client, times(1)).getFileServiceEndpoint(any());
}
@Test
public void testNewFolder() throws Exception {
HttpSession mockedSession = mock(HttpSession.class);
ReflectionTestUtils.setField(nodesController, "session", mockedSession);
when(mockedSession.getAttribute("user_data")).thenReturn(mock(User.class));
mockMvc.perform(post("/folder")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"parentPath\": \"/parent\", \"name\": \"newFolder\"}"))
.andExpect(status().is2xxSuccessful());
verify(client, times(1)).createNode(any());
}
private ResultActions testMoveNode() throws Exception { private ResultActions testMoveNode() throws Exception {
JobSummary job = new JobSummary(); JobSummary job = new JobSummary();
......
...@@ -51,6 +51,14 @@ public class NodeInfoTest { ...@@ -51,6 +51,14 @@ public class NodeInfoTest {
testNodeLength(6963696737150000L, "6.2 PB"); testNodeLength(6963696737150000L, "6.2 PB");
} }
@Test
public void testUrlEncoding() {
DataNode node = new DataNode();
node.setUri("vos://example.com!vospace/my%23node");
NodeInfo nodeInfo = new NodeInfo(node, new User(), AUTHORITY);
assertEquals("my#node", nodeInfo.getName());
}
private void testNodeLength(long bytes, String expectedText) { private void testNodeLength(long bytes, String expectedText) {
DataNode node = getDataNode(); DataNode node = getDataNode();
setLength(node, bytes); setLength(node, bytes);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment