Skip to content
PutFileControllerTest.java 11.5 KiB
Newer Older
Sonia Zorba's avatar
Sonia Zorba committed
/*
 * 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.controller;

import it.inaf.ia2.transfer.persistence.model.FileInfo;
import it.inaf.ia2.transfer.persistence.FileDAO;
import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.oats.vospace.exception.QuotaExceededException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Optional;
import java.util.UUID;
import javax.servlet.ServletInputStream;
import net.ivoa.xml.uws.v1.ExecutionPhase;
import org.assertj.core.util.Files;
import org.junit.jupiter.api.AfterAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
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 org.mockito.Mockito;
import static org.mockito.Mockito.mock;
Sonia Zorba's avatar
Sonia Zorba committed
import static org.mockito.Mockito.spy;
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.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.web.servlet.MockMvc;
Sonia Zorba's avatar
Sonia Zorba committed
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;
Sonia Zorba's avatar
Sonia Zorba committed
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;

@SpringBootTest
@AutoConfigureMockMvc
public class PutFileControllerTest {

    @MockBean
    private FileDAO fileDao;
    @MockBean
    private JobDAO jobDAO;
    @Autowired
    private MockMvc mockMvc;

    private static File temporaryDirectory;

    @BeforeAll
    public static void setUp() {
        temporaryDirectory = Files.newTemporaryFolder();
    }

    @AfterAll
    public static void tearDown() {
        FileSystemUtils.deleteRecursively(temporaryDirectory);
    }

    @Test
    public void putGenericFile() throws Exception {

        when(fileDao.getRemainingQuota(any())).thenReturn(null);
        
        String randomFileName = UUID.randomUUID().toString();
        createBaseFileInfo(randomFileName);

        MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes());

        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile))
                .andDo(print())
                .andExpect(status().isOk());

        File file = Path.of(getTestFilePath(randomFileName)).toFile();

        assertTrue(file.exists());
        assertEquals("content", Files.contentOf(file, StandardCharsets.UTF_8));
        assertTrue(file.delete());
    public void putGenericFileWithNameConflictExtension() throws Exception {
        putGenericFileWithNameConflict("test.txt", "test-1.txt", "test-2.txt");
    }

    @Test
    public void putGenericFileWithNameConflictNoExtension() throws Exception {
        putGenericFileWithNameConflict("test", "test-1", "test-2");
    }
Sonia Zorba's avatar
Sonia Zorba committed
    private void putGenericFileWithNameConflict(String name1, String name2, String name3) throws Exception {
        when(fileDao.getRemainingQuota(any())).thenReturn(null);
        
        createBaseFileInfo(name1);

        MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes());

        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile))
                .andDo(print())
                .andExpect(status().isOk());

        File file = Path.of(getTestFilePath(name1)).toFile();

        assertTrue(file.exists());
        assertEquals("content", Files.contentOf(file, StandardCharsets.UTF_8));
        MockMultipartFile fakeFile2 = new MockMultipartFile("file", "test.txt", "text/plain", "content2".getBytes());
        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile2))
                .andDo(print())
                .andExpect(status().isOk());

        File file2 = Path.of(getTestFilePath(name2)).toFile();
        assertTrue(file2.exists());
        assertEquals("content2", Files.contentOf(file2, StandardCharsets.UTF_8));

        MockMultipartFile fakeFile3 = new MockMultipartFile("file", "test.txt", "text/plain", "content3".getBytes());

        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile3))
                .andDo(print())
                .andExpect(status().isOk());

        File file3 = Path.of(getTestFilePath(name3)).toFile();
        assertTrue(file3.exists());
        assertEquals("content3", Files.contentOf(file3, StandardCharsets.UTF_8));

        assertTrue(file.delete());
        assertTrue(file2.delete());
        assertTrue(file3.delete());
    }

    @Test
    public void putGenericFileWithJobId() throws Exception {
        when(fileDao.getRemainingQuota(any())).thenReturn(null);
        
        when(jobDAO.isJobExisting("pippo10")).thenReturn(false);
        when(jobDAO.isJobExisting("pippo5")).thenReturn(true);

        String randomFileName = UUID.randomUUID().toString();
        createBaseFileInfo(randomFileName);

        MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", "text/plain", "content".getBytes());
        // Try with invalid jobid
        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile).param("jobId", "pippo10"))
                .andDo(print())
                .andExpect(status().is4xxClientError());
        verify(jobDAO, times(1)).isJobExisting(eq("pippo10"));
        // Retry with valid jobid        
        mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile).param("jobId", "pippo5"))
                .andDo(print())
                .andExpect(status().is2xxSuccessful());
        verify(jobDAO, times(1)).isJobExisting(eq("pippo5"));
        verify(jobDAO, times(1)).updateJobPhase(eq(ExecutionPhase.COMPLETED), any());

        File file = Path.of(getTestFilePath(randomFileName)).toFile();

        assertTrue(file.exists());
        assertEquals("content", Files.contentOf(file, StandardCharsets.UTF_8));
        assertTrue(file.delete());
    }

Sonia Zorba's avatar
Sonia Zorba committed
    @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());
    }
    
    @Test
    public void testQuotaExceededMultipart() throws Exception {

        when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l);

        createBaseFileInfo();

        MockMultipartFile fakeFile = new MockMultipartFile("file", "test.txt", null, "content".getBytes());

        Exception ex = mockMvc.perform(putMultipart("/path/to/test.txt")
                .file(fakeFile))
                .andDo(print())
                .andExpect(status().is5xxServerError())
                .andReturn().getResolvedException();

        verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));

        assertTrue(ex instanceof QuotaExceededException);
    }

    @Test
    public void testQuotaExceededStream() throws Exception {

        when(fileDao.getRemainingQuota(eq("/path/to"))).thenReturn(0l);

        createBaseFileInfo();

        MockHttpServletRequestBuilder streamBuilder = put("/path/to/test.txt");
        streamBuilder.with(new RequestPostProcessor() {
            @Override
            public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
                MockHttpServletRequest spyRequest = spy(request);
                ByteArrayInputStream bais = new ByteArrayInputStream("some data".getBytes());
                ServletInputStream sis = mock(ServletInputStream.class);
                try {
                    when(sis.transferTo(any())).thenAnswer(i -> bais.transferTo(i.getArgument(0)));
                } catch (IOException ex) {
                }
                Mockito.doReturn(sis).when(spyRequest).getInputStream();
                return spyRequest;
            }
        });

        Exception ex = mockMvc.perform(streamBuilder)
                .andDo(print())
                .andExpect(status().is5xxServerError())
                .andReturn().getResolvedException();

        verify(fileDao, times(1)).getRemainingQuota(eq("/path/to"));

        assertTrue(ex instanceof QuotaExceededException);
Sonia Zorba's avatar
Sonia Zorba committed

    private FileInfo createBaseFileInfo() {
        String randomFileName = UUID.randomUUID().toString();
        return createBaseFileInfo(randomFileName);
    }
    
    private FileInfo createBaseFileInfo(String fileName) {        
        FileInfo fileInfo = new FileInfo();
        fileInfo.setOsPath(getTestFilePath(fileName));
        fileInfo.setVirtualPath("/path/to/" + fileName);
        when(fileDao.getFileInfo(any())).thenReturn(Optional.of(fileInfo));
Nicola Fulvio Calabria's avatar
Nicola Fulvio Calabria committed
        when(fileDao.setBusy(any(), any())).thenReturn(1);
        return fileInfo;
    }

    private String getTestFilePath(String fileName) {
Sonia Zorba's avatar
Sonia Zorba committed
        return temporaryDirectory.toPath().resolve("subdir").resolve(fileName).toFile().getAbsolutePath();
    private MockMultipartHttpServletRequestBuilder putMultipart(String uri) {
        MockMultipartHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(uri);
        builder.with(new RequestPostProcessor() {
            @Override
            public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
                request.setMethod("PUT");
                return request;
            }
        });
        return builder;
    }
}