Skip to content
TransferControllerTest.java 14.7 KiB
Newer Older
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.datamodel.NodeProperties;
import it.inaf.oats.vospace.exception.ErrorSummaryFactory;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.JobDAO;
import it.inaf.oats.vospace.persistence.LocationDAO;
import it.inaf.oats.vospace.persistence.NodeDAO;
import it.inaf.oats.vospace.persistence.model.Location;
import it.inaf.oats.vospace.persistence.model.LocationType;
import it.inaf.oats.vospace.persistence.model.Storage;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Optional;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import net.ivoa.xml.uws.v1.JobSummary;
import net.ivoa.xml.uws.v1.Jobs;
import net.ivoa.xml.uws.v1.ShortJobDescription;
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;
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.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;
import java.util.List;
import net.ivoa.xml.uws.v1.ErrorSummary;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import static org.mockito.ArgumentMatchers.argThat;

@SpringBootTest
@AutoConfigureMockMvc
@ContextConfiguration(classes = {TokenFilterConfig.class})
@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true")
public class TransferControllerTest {

    @MockBean
    private JobDAO jobDao;

    @MockBean
    private NodeDAO nodeDao;

    @MockBean
    private LocationDAO locationDao;

    @MockBean
    private AsyncTransferService asyncTransfService;

    @Autowired
    private MockMvc mockMvc;
    @BeforeEach
    public void init() {
        Location asyncLocation = new Location();
        asyncLocation.setType(LocationType.ASYNC);
        asyncLocation.setId(1);
        when(locationDao.getNodeLocation(eq("/mynode"))).thenReturn(Optional.of(asyncLocation));

        Location portalLocation = new Location();
        portalLocation.setType(LocationType.PORTAL);
        portalLocation.setId(2);
        Storage portalStorage = new Storage();
        portalStorage.setHostname("archive.lbto.org");
        portalStorage.setBaseUrl("/files");
        portalLocation.setSource(portalStorage);
        when(locationDao.getNodeLocation(eq("/portalnode"))).thenReturn(Optional.of(portalLocation));
        when(locationDao.findPortalLocation(any())).thenReturn(Optional.of(portalLocation));
    }

    @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 testPullToVoSpaceTape() throws Exception {
        testPullToVoSpace("/mynode", getResourceFileContent("pullToVoSpace-tape.xml"));

        verify(asyncTransfService, times(1)).startJob(any());
    }

    @Test
    public void testPullToVoSpacePortal() throws Exception {

        when(nodeDao.getNodeOsName(eq("/portalnode"))).thenReturn("file.fits");

        testPullToVoSpace("/portalnode", getResourceFileContent("pullToVoSpace-portal.xml"));

        verify(nodeDao, times(1)).setNodeLocation(eq("/portalnode"), eq(2), eq("lbcr.20130512.060722.fits.gz"));
        verify(jobDao, times(1)).updateJob(argThat(j -> {
            assertTrue(j.getResults().get(0).getHref().startsWith("http://archive.lbto.org"));
            assertEquals(ExecutionPhase.EXECUTING, j.getPhase());
            return true;
        }));
    }

    private void testPullToVoSpace(String path, String requestBody) throws Exception {
        Node node = mockPublicDataNode();
        when(nodeDao.listNode(eq(path))).thenReturn(Optional.of(node));

        String redirect = mockMvc.perform(post("/transfers?PHASE=RUN")
                .header("Authorization", "Bearer user1_token")
                .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 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);
        Property ownerProp = new Property();
        ownerProp.setUri(NodeProperties.CREATOR_URI);
        ownerProp.setValue("user1");
        node.getProperties().add(ownerProp);
        Property groupProp = new Property();
        groupProp.setUri(NodeProperties.GROUP_WRITE_URI);
        groupProp.setValue("group1");
        node.getProperties().add(groupProp);
    @Test
    public void testGetJob() throws Exception {

        JobSummary job = new JobSummary();

        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));

        String xml = mockMvc.perform(get("/transfers/123")
                .accept(MediaType.APPLICATION_XML))
                .andDo(print())
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();

        Document doc = loadDocument(xml);
        assertEquals("uws:job", doc.getDocumentElement().getNodeName());

        verify(jobDao, times(1)).getJob(eq("123"));
    }
    
    @Test
    public void testErrorEndpoint() throws Exception {        
        JobSummary job = new JobSummary();
        job.setJobId("123");
        job.setPhase(ExecutionPhase.EXECUTING);
        ErrorSummary e = ErrorSummaryFactory.newErrorSummary(
                new PermissionDeniedException("/pippo1/pippo2")
        );
        job.setErrorSummary(e);          

        when(jobDao.getJob(eq("123"))).thenReturn(Optional.of(job));
                
        String response = mockMvc.perform(get("/transfers/123/error")
                .accept(MediaType.TEXT_PLAIN_VALUE))
                .andDo(print())
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        
        assertEquals("Job is not in ERROR phase", response);
        
        job.setPhase(ExecutionPhase.ERROR);
        
        response = mockMvc.perform(get("/transfers/123/error")
                .accept(MediaType.TEXT_PLAIN_VALUE))
                .andDo(print())
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        
        assertEquals(e.getDetailMessage(), response);
        
        e.setHasDetail(false);        
        
        response = mockMvc.perform(get("/transfers/123/error")
                .accept(MediaType.TEXT_PLAIN_VALUE))
                .andDo(print())
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
        
        assertEquals("No error details available", response);
        
        when(jobDao.getJob(eq("124"))).thenReturn(Optional.ofNullable(null));
        
        mockMvc.perform(get("/transfers/124/error")
                .accept(MediaType.TEXT_PLAIN_VALUE))
                .andDo(print())
                .andExpect(status().is4xxClientError());        
    }
    @Test
    public void testGetJobs() throws Exception {
        when(jobDao.getJobs(eq("user1"), any(), any(), any(), any()))
                .thenReturn(this.getFakeJobs());
        mockMvc.perform(get("/transfers")
                .header("Authorization", "Bearer user1_token")
                .param("LAST", "-3")
                .accept(MediaType.APPLICATION_XML))
                .andDo(print())
                .andExpect(status().is4xxClientError());
        String xml2 = mockMvc.perform(get("/transfers")
                .header("Authorization", "Bearer user1_token")
                .accept(MediaType.APPLICATION_XML))
                .andDo(print())
                .andExpect(status().isOk())
                .andReturn().getResponse().getContentAsString();
    }

    @Test
    public void testSyncTransferUrlParamsMode() throws Exception {

        Node node = mockPublicDataNode();
        when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(node));

        mockMvc.perform(get("/synctrans")
                .header("Authorization", "Bearer user1_token")
                .param("TARGET", "vos://example.com!vospace/mynode")
                .param("DIRECTION", "pullFromVoSpace")
                // testing duplicated protocol (CADC client)
                .param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget")
                .param("PROTOCOL", "ivo://ivoa.net/vospace/core#httpget"))
                .andExpect(status().is3xxRedirection());
    }

    private Jobs getFakeJobs() {
        Jobs jobs = new Jobs();
        jobs.setVersion("1.1");
        List<ShortJobDescription> sjdList = jobs.getJobref();
        sjdList.add(getFakeSJD1());

        return jobs;
    }

    private ShortJobDescription getFakeSJD1() {
        ShortJobDescription sjd = new ShortJobDescription();
        sjd.setId("pippo1");
        sjd.setPhase(ExecutionPhase.QUEUED);
        sjd.setOwnerId("user1");
        sjd.setType(JobService.JobDirection.pullFromVoSpace.toString());

        LocalDateTime now = LocalDateTime.now();
        Timestamp ts = Timestamp.valueOf(now);
        sjd.setCreationTime(JobDAO.toXMLGregorianCalendar(ts));

        return sjd;
    }

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