Newer
Older
/*
* 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.service;
import it.inaf.ia2.transfer.auth.TokenPrincipal;
import it.inaf.ia2.transfer.exception.InsufficientStorageException;
import it.inaf.ia2.transfer.persistence.FileDAO;
Sonia Zorba
committed
import it.inaf.ia2.transfer.persistence.JobDAO;
import it.inaf.ia2.transfer.persistence.LocationDAO;
import it.inaf.ia2.transfer.persistence.model.FileInfo;
Sonia Zorba
committed
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
Sonia Zorba
committed
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Sonia Zorba
committed
import java.util.Map;
import java.util.function.Function;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.kamranzafar.jtar.TarEntry;
import org.kamranzafar.jtar.TarInputStream;
import static org.mockito.ArgumentMatchers.any;
Sonia Zorba
committed
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
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.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
Sonia Zorba
committed
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.util.FileSystemUtils;
Sonia Zorba
committed
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestTemplate;
@SpringBootTest
@ContextConfiguration(initializers = ArchiveServiceTest.TestPropertiesInitializer.class)
public class ArchiveServiceTest {
Sonia Zorba
committed
private JobDAO jobDAO;
private FileDAO fileDAO;
Sonia Zorba
committed
private LocationDAO locationDAO;
Sonia Zorba
committed
private RestTemplate restTemplate;
private AuthorizationService authorizationService;
private ArchiveService archiveService;
private static File tmpDir;
@BeforeAll
public static void setUpClass() throws Exception {
tmpDir = Files.createTempDirectory("generated").toFile();
}
@AfterAll
public static void tearDownClass() throws Exception {
FileSystemUtils.deleteRecursively(tmpDir);
}
@Test
public void testTarGeneration() throws Exception {
Sonia Zorba
committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
testArchiveGeneration(ArchiveJob.Type.TAR, "tar", is -> new TestArchiveHandler<TarInputStream, TarEntry>(new TarInputStream(is)) {
@Override
TarEntry getNextEntry() throws IOException {
return getInputStream().getNextEntry();
}
@Override
String getName(TarEntry entry) {
return entry.getName();
}
@Override
boolean isDirectory(TarEntry entry) {
return entry.isDirectory();
}
});
}
@Test
public void testZipGeneration() throws Exception {
testArchiveGeneration(ArchiveJob.Type.ZIP, "zip", is -> new TestArchiveHandler<ZipInputStream, ZipEntry>(new ZipInputStream(is)) {
@Override
ZipEntry getNextEntry() throws IOException {
return getInputStream().getNextEntry();
}
@Override
String getName(ZipEntry entry) {
return entry.getName();
}
@Override
boolean isDirectory(ZipEntry entry) {
return entry.isDirectory();
}
});
}
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
@Test
public void testArchiveQuotaExceeded() throws Exception {
ArchiveJob job = new ArchiveJob();
job.setPrincipal(new TokenPrincipal("user2", "token2"));
job.setJobId("job2");
job.setType(ArchiveJob.Type.ZIP);
job.setVosPaths(Arrays.asList("/ignore"));
File user2Dir = tmpDir.toPath().resolve("user2").toFile();
user2Dir.mkdir();
File fillQuotaFile = user2Dir.toPath().resolve("fillQuotaFile").toFile();
// create a file bigger than test quota limit (20 KB)
try (FileInputStream fis = new FileInputStream("/dev/zero");
FileOutputStream fos = new FileOutputStream(fillQuotaFile)) {
byte[] junk = fis.readNBytes(20 * 1024);
fos.write(junk);
}
Assertions.assertThrows(InsufficientStorageException.class, () -> {
archiveService.createArchive(job);
});
}
Sonia Zorba
committed
private static abstract class TestArchiveHandler<I extends InputStream, E> {
private final I is;
TestArchiveHandler(I is) {
this.is = is;
}
I getInputStream() {
return is;
}
abstract E getNextEntry() throws IOException;
abstract String getName(E entry);
abstract boolean isDirectory(E entry);
}
private <I extends InputStream, E> void testArchiveGeneration(ArchiveJob.Type type, String extension, Function<FileInputStream, TestArchiveHandler<I, E>> testArchiveGetter) throws Exception {
String parent = "/path/to";
File tmpParent = tmpDir.toPath().resolve("test1").toFile();
File file1 = createFile(tmpParent, "dir1/a/b/file1");
File file2 = createFile(tmpParent, "dir1/a/b/file2");
File file3 = createFile(tmpParent, "dir2/c/file3");
File file4 = createFile(tmpParent, "dir2/c/file4");
File file5 = createFile(tmpParent, "dir2/c/d/file5");
File file6 = createFile(tmpParent, "file6");
Sonia Zorba
committed
File file7 = createFile(tmpParent, "portal-file");
ArchiveJob job = new ArchiveJob();
job.setPrincipal(new TokenPrincipal("user1", "token1"));
job.setJobId("abcdef");
Sonia Zorba
committed
job.setType(type);
job.setVosPaths(Arrays.asList(parent + "/dir1", parent + "/dir2", parent + "/file6"));
when(authorizationService.isDownloadable(any(), any())).thenReturn(true);
List<FileInfo> fileInfos = new ArrayList<>();
addFileInfo(fileInfos, parent + "/file6", file6);
addDirInfo(fileInfos, parent + "/dir1");
addDirInfo(fileInfos, parent + "/dir1/a");
addDirInfo(fileInfos, parent + "/dir1/a/b");
addFileInfo(fileInfos, parent + "/dir1/a/b/file1", file1);
addFileInfo(fileInfos, parent + "/dir1/a/b/file2", file2);
addDirInfo(fileInfos, parent + "/dir2");
addDirInfo(fileInfos, parent + "/dir2/c");
addFileInfo(fileInfos, parent + "/dir2/c/file3", file3);
addFileInfo(fileInfos, parent + "/dir2/c/file4", file4);
addDirInfo(fileInfos, parent + "/dir2/c/d");
addFileInfo(fileInfos, parent + "/dir2/c/d/file5", file5);
Sonia Zorba
committed
addFileInfo(fileInfos, parent + "/portal-file", file7).setLocationId(1);
when(fileDAO.getArchiveFileInfos(any())).thenReturn(fileInfos);
Sonia Zorba
committed
when(locationDAO.getPortalLocationUrls()).thenReturn(Map.of(1, "http://portal/base/url"));
doAnswer(invocation -> {
ResponseExtractor responseExtractor = invocation.getArgument(3);
ClientHttpResponse mockedResponse = mock(ClientHttpResponse.class);
when(mockedResponse.getBody()).thenReturn(new ByteArrayInputStream("some data".getBytes()));
responseExtractor.extractData(mockedResponse);
return null;
}).when(restTemplate).execute(eq("http://portal/base/url/portal-file"), eq(HttpMethod.GET),
any(RequestCallback.class), any(ResponseExtractor.class), any(Object[].class));
archiveService.createArchive(job);
File result = tmpDir.toPath().resolve("user1").resolve("abcdef." + extension).toFile();
assertTrue(result.exists());
// verify result structure
List<String> expectedSequence = Arrays.asList("file6", "dir1/", "dir1/a/", "dir1/a/b/",
"dir1/a/b/file1", "dir1/a/b/file2", "dir2/", "dir2/c/", "dir2/c/file3", "dir2/c/file4",
Sonia Zorba
committed
"dir2/c/d/", "dir2/c/d/file5", "portal-file");
Sonia Zorba
committed
TestArchiveHandler<I, E> testArchiveHandler = testArchiveGetter.apply(new FileInputStream(result));
try ( InputStream is = testArchiveHandler.getInputStream()) {
E entry;
while ((entry = testArchiveHandler.getNextEntry()) != null) {
assertFalse(i >= expectedSequence.size(), "Found more entries than in expected sequence");
Sonia Zorba
committed
assertEquals(expectedSequence.get(i), testArchiveHandler.getName(entry));
if (!testArchiveHandler.isDirectory(entry)) {
assertEquals("some data", new String(is.readAllBytes()));
Sonia Zorba
committed
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
assertFalse(i < expectedSequence.size(), "Found less entries than in expected sequence");
}
Sonia Zorba
committed
private FileInfo addFileInfo(List<FileInfo> fileInfos, String vosPath, File file) {
FileInfo fileInfo = new FileInfo();
fileInfo.setOsPath(file.getAbsolutePath());
fileInfo.setVirtualPath(vosPath);
Sonia Zorba
committed
fileInfo.setVirtualName(vosPath.substring(vosPath.lastIndexOf("/") + 1));
fileInfos.add(fileInfo);
Sonia Zorba
committed
return fileInfo;
Sonia Zorba
committed
private FileInfo addDirInfo(List<FileInfo> fileInfos, String vosPath) {
FileInfo fileInfo = new FileInfo();
fileInfo.setVirtualPath(vosPath);
fileInfo.setDirectory(true);
fileInfos.add(fileInfo);
Sonia Zorba
committed
return fileInfo;
}
private File createFile(File parent, String path) throws Exception {
parent.mkdir();
String[] files = path.split("/");
for (int i = 0; i < files.length; i++) {
File file = parent.toPath().resolve(files[i]).toFile();
if (i == files.length - 1) {
// test os_path different from vos_path
file.renameTo(file.getParentFile().toPath().resolve(file.getName() + "-renamed").toFile());
file.createNewFile();
Files.write(file.toPath(), "some data".getBytes());
return file;
} else {
file.mkdir();
parent = file;
}
}
throw new IllegalStateException("Files have to be created");
}
/**
* @TestPropertySource annotation can't be used in this test because we need
* to set the generated.dir property dynamically (since the test directory
* is generated by the @BeforeAll method), so this inner class is used to
* perform test property initialization.
*/
static class TestPropertiesInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of("generated.dir=" + tmpDir.getAbsolutePath(),
"generated.dir.max-size=20KB", "upload_location_id=3")
.applyTo(configurableApplicationContext.getEnvironment());
}
}