/* * This file is part of vospace-rest * Copyright (C) 2021 Istituto Nazionale di Astrofisica * SPDX-License-Identifier: GPL-3.0-or-later */ package it.inaf.oats.vospace; import it.inaf.oats.vospace.persistence.NodeDAO; import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import net.ivoa.xml.vospace.v2.Property; import it.inaf.oats.vospace.datamodel.NodeProperties; import net.ivoa.xml.vospace.v2.UnstructuredDataNode; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; 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 static org.mockito.ArgumentMatchers.any; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.LinkNode; import java.util.List; import java.util.Objects; import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import org.mockito.ArgumentCaptor; import static org.mockito.Mockito.times; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.ContextConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @SpringBootTest @ContextConfiguration(classes = {TokenFilterConfig.class}) @TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") @AutoConfigureMockMvc public class CreateNodeControllerTest { @MockBean private NodeDAO nodeDao; @SpyBean @Autowired private CreateNodeController controller; @Autowired private MockMvc mockMvc; private ContainerNode getContainerParentNode(String path) { ContainerNode parentNode = new ContainerNode(); // Set parent node address at / parentNode.setUri("vos://example.com!vospace" + path); // Set groupwrite property Property groups = new Property(); groups.setUri("ivo://ivoa.net/vospace/core#groupwrite"); groups.setValue("group1 group2"); parentNode.setProperties(List.of(groups)); return parentNode; } private ContainerNode getContainerParentNodeWithCreator(String path) { ContainerNode parentNode = new ContainerNode(); // Set parent node address at / parentNode.setUri("vos://example.com!vospace" + path); Property creator = new Property(); creator.setUri("ivo://ivoa.net/vospace/core#creator"); creator.setValue("user2"); parentNode.setProperties(List.of(creator)); return parentNode; } private LinkNode getLinkParentNode(String path) { LinkNode parentNode = new LinkNode(); // Set parent node address at / parentNode.setUri("vos://example.com!vospace" + path); // Set groupwrite property Property groups = new Property(); groups.setUri("ivo://ivoa.net/vospace/core#groupwrite"); groups.setValue("group1 group2"); parentNode.setProperties(List.of(groups)); return parentNode; } @Test public void testFromJsonToXml() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.json"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()); verifyArguments(); } @Test public void testFromXmlToJson() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk()); verifyArguments(); verify(nodeDao, times(1)).createNode(any()); } @Test public void testFromXmlToXml() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()); verifyArguments(); verify(nodeDao, times(1)).createNode(any()); } @Test public void testFromJsonToJson() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.json"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk()); verifyArguments(); verify(nodeDao, times(1)).createNode(any()); } @Test public void testURIConsistence() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata2") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyArguments(); } @Test public void testCreateInternalLinkNode() throws Exception { String requestBody = getResourceFileContent("create-internal-link-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/myInternalLink") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()); verifyLinkArguments("vos://example.com!vospace/myDummyDataNode1"); } @Test public void testCreateExternalLinkNode() throws Exception { String requestBody = getResourceFileContent("create-external-link-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/myExternalLink") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyLinkArguments("vos://external.com!vospace/myDummyDataNode1"); } @Test public void testCreateLinkNodeNoTarget() throws Exception { String requestBody = getResourceFileContent("create-link-node-notarget.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/myNoTargetLink") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyLinkArguments(null); } @Test public void testCreateLinkNodeExternalHttp() throws Exception { String requestBody = getResourceFileContent("create-link-node-external-http.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/myExternalHttpLink") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isOk()); verifyLinkArguments("http://www.external.com/files/file.txt"); } @Test public void testNodeAlreadyExisting() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); when(nodeDao.listNode(eq("/mydata1"))) .thenReturn(Optional.of(getContainerParentNode("/mydata1"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyArguments(); } @Test public void testContainerNotFound() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.ofNullable(null)); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyArguments(); } @Test public void testLinkNodeFound() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getLinkParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyArguments(); } @Test public void testPermissionDenied() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNode("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user1_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); verifyArguments(); } @Test public void testWriteWithOnlyOwnership() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNodeWithCreator("/"))); mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is2xxSuccessful()); verifyArguments(); verify(nodeDao, times(1)).createNode(any()); } @Test public void testWriteOwnerAbsent() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNodeWithCreator("/"))); // no node creator specified in xml file mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is2xxSuccessful()); // assert creator properties now matches user2 verify(nodeDao, times(1)).createNode(argThat(node -> { UnstructuredDataNode udn = (UnstructuredDataNode) node; String creator = NodeProperties.getNodePropertyByURI( udn, NodeProperties.CREATOR_URI); return (creator != null && creator.equals("user2")); })); } @Test public void testGroupPropertiesAbsent() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); ContainerNode cdn = getContainerParentNode("/"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(cdn)); // no node creator specified in xml file mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is2xxSuccessful()); // assert creator properties now matches user2 verify(nodeDao, times(1)).createNode(argThat(node -> { UnstructuredDataNode udn = (UnstructuredDataNode) node; String groupRead = NodeProperties.getNodePropertyByURI( udn, NodeProperties.GROUP_READ_URI); String groupWrite = NodeProperties.getNodePropertyByURI( udn, NodeProperties.GROUP_WRITE_URI); return (groupRead == null && groupWrite.equals("group1 group2")); })); } @Test public void testWriteOwnerMismatch() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node-user1.xml"); when(nodeDao.listNode(eq("/"))) .thenReturn(Optional.of(getContainerParentNodeWithCreator("/"))); // no node creator specified in xml file mockMvc.perform(put("/nodes/mydata1") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().is4xxClientError()); // assert createNode is not called verify(nodeDao, times(0)).createNode(any()); } @Test public void testSubPath() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml") .replace("/mydata1", "/mydata1/anothernode"); mockMvc.perform(put("/nodes/mydata1/anothernode") .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isNotFound()); // Using ArgumentCaptor for verifying multiple method invocations ArgumentCaptor argCaptor = ArgumentCaptor.forClass(String.class); verify(nodeDao, times(2)).listNode(argCaptor.capture()); assertEquals("/mydata1/anothernode", argCaptor.getAllValues().get(0)); assertEquals("/mydata1", argCaptor.getAllValues().get(1)); } @Test public void testIllegalChars() throws Exception { String requestBody = getResourceFileContent("create-unstructured-data-node.xml") .replace("/mydata1", "/mydata1/?anothernode"); String message = mockMvc.perform(put(new URI("/nodes/mydata1/%3Fanothernode")) .header("Authorization", "Bearer user2_token") .content(requestBody) .contentType(MediaType.APPLICATION_XML) .accept(MediaType.APPLICATION_XML)) .andDo(print()) .andExpect(status().isBadRequest()) .andReturn().getResolvedException().getMessage(); assertTrue(message.contains("contains an illegal character")); } private void verifyArguments() { verify(controller).createNode( argThat(node -> { UnstructuredDataNode udn = (UnstructuredDataNode) node; Property property = udn.getProperties().get(0); return "vos:UnstructuredDataNode".equals(udn.getType()) && "test value".equals(property.getValue()) && "ivo://ivoa.net/vospace/core#description".equals(property.getUri()); }), any()); } private void verifyLinkArguments(String target) { verify(controller).createNode( argThat(node -> { LinkNode linkNode = (LinkNode) node; Property property = linkNode.getProperties().get(0); return "vos:LinkNode".equals(linkNode.getType()) && "test value".equals(property.getValue()) && "ivo://ivoa.net/vospace/core#description".equals(property.getUri()) && Objects.equals(target, linkNode.getTarget()); }), any()); } protected static String getResourceFileContent(String fileName) throws Exception { try (InputStream in = CreateNodeControllerTest.class.getClassLoader().getResourceAsStream(fileName)) { return new String(in.readAllBytes(), StandardCharsets.UTF_8); } } }