260 lines
11 KiB
Java
260 lines
11 KiB
Java
package com.yoyuzh.files;
|
|
|
|
import com.yoyuzh.PortalBackendApplication;
|
|
import com.yoyuzh.auth.User;
|
|
import com.yoyuzh.auth.UserRepository;
|
|
import org.junit.jupiter.api.BeforeEach;
|
|
import org.junit.jupiter.api.Test;
|
|
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.http.MediaType;
|
|
import org.springframework.test.web.servlet.MockMvc;
|
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Path;
|
|
import java.time.LocalDateTime;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
|
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.anonymous;
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
|
|
|
@SpringBootTest(
|
|
classes = PortalBackendApplication.class,
|
|
properties = {
|
|
"spring.datasource.url=jdbc:h2:mem:file_share_api_test;MODE=MySQL;DB_CLOSE_DELAY=-1",
|
|
"spring.datasource.driver-class-name=org.h2.Driver",
|
|
"spring.datasource.username=sa",
|
|
"spring.datasource.password=",
|
|
"spring.jpa.hibernate.ddl-auto=create-drop",
|
|
"app.jwt.secret=0123456789abcdef0123456789abcdef",
|
|
"app.storage.root-dir=./target/test-storage-file-share"
|
|
}
|
|
)
|
|
@AutoConfigureMockMvc
|
|
class FileShareControllerIntegrationTest {
|
|
|
|
private static final Path STORAGE_ROOT = Path.of("./target/test-storage-file-share").toAbsolutePath().normalize();
|
|
private Long sharedFileId;
|
|
|
|
@Autowired
|
|
private MockMvc mockMvc;
|
|
|
|
@Autowired
|
|
private UserRepository userRepository;
|
|
|
|
@Autowired
|
|
private StoredFileRepository storedFileRepository;
|
|
@Autowired
|
|
private FileBlobRepository fileBlobRepository;
|
|
|
|
@Autowired
|
|
private FileShareLinkRepository fileShareLinkRepository;
|
|
|
|
@BeforeEach
|
|
void setUp() throws Exception {
|
|
fileShareLinkRepository.deleteAll();
|
|
storedFileRepository.deleteAll();
|
|
fileBlobRepository.deleteAll();
|
|
userRepository.deleteAll();
|
|
if (Files.exists(STORAGE_ROOT)) {
|
|
try (var paths = Files.walk(STORAGE_ROOT)) {
|
|
paths.sorted((left, right) -> right.compareTo(left)).forEach(path -> {
|
|
try {
|
|
Files.deleteIfExists(path);
|
|
} catch (Exception ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
Files.createDirectories(STORAGE_ROOT);
|
|
|
|
User owner = new User();
|
|
owner.setUsername("alice");
|
|
owner.setEmail("alice@example.com");
|
|
owner.setPhoneNumber("13800138000");
|
|
owner.setPasswordHash("encoded-password");
|
|
owner.setCreatedAt(LocalDateTime.now());
|
|
owner = userRepository.save(owner);
|
|
|
|
User recipient = new User();
|
|
recipient.setUsername("bob");
|
|
recipient.setEmail("bob@example.com");
|
|
recipient.setPhoneNumber("13800138001");
|
|
recipient.setPasswordHash("encoded-password");
|
|
recipient.setCreatedAt(LocalDateTime.now());
|
|
recipient = userRepository.save(recipient);
|
|
|
|
FileBlob blob = new FileBlob();
|
|
blob.setObjectKey("blobs/share-notes");
|
|
blob.setContentType("text/plain");
|
|
blob.setSize(5L);
|
|
blob.setCreatedAt(LocalDateTime.now());
|
|
blob = fileBlobRepository.save(blob);
|
|
|
|
StoredFile file = new StoredFile();
|
|
file.setUser(owner);
|
|
file.setFilename("notes.txt");
|
|
file.setPath("/docs");
|
|
file.setContentType("text/plain");
|
|
file.setSize(5L);
|
|
file.setDirectory(false);
|
|
file.setBlob(blob);
|
|
sharedFileId = storedFileRepository.save(file).getId();
|
|
|
|
Path blobPath = STORAGE_ROOT.resolve("blobs").resolve("share-notes");
|
|
Files.createDirectories(blobPath.getParent());
|
|
Files.writeString(blobPath, "hello", StandardCharsets.UTF_8);
|
|
}
|
|
|
|
@Test
|
|
void shouldCreateInspectAndImportSharedFile() throws Exception {
|
|
String response = mockMvc.perform(post("/api/files/{fileId}/share-links", sharedFileId)
|
|
.with(user("alice")))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.code").value(0))
|
|
.andExpect(jsonPath("$.data.token").isNotEmpty())
|
|
.andExpect(jsonPath("$.data.filename").value("notes.txt"))
|
|
.andReturn()
|
|
.getResponse()
|
|
.getContentAsString();
|
|
|
|
String token = com.jayway.jsonpath.JsonPath.read(response, "$.data.token");
|
|
|
|
mockMvc.perform(get("/api/files/share-links/{token}", token))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.token").value(token))
|
|
.andExpect(jsonPath("$.data.filename").value("notes.txt"))
|
|
.andExpect(jsonPath("$.data.ownerUsername").value("alice"));
|
|
|
|
mockMvc.perform(post("/api/files/share-links/{token}/import", token)
|
|
.with(anonymous())
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content("""
|
|
{
|
|
"path": "/下载"
|
|
}
|
|
"""))
|
|
.andExpect(status().isUnauthorized());
|
|
|
|
mockMvc.perform(post("/api/files/share-links/{token}/import", token)
|
|
.with(user("bob"))
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content("""
|
|
{
|
|
"path": "/下载"
|
|
}
|
|
"""))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.filename").value("notes.txt"))
|
|
.andExpect(jsonPath("$.data.path").value("/下载"));
|
|
|
|
mockMvc.perform(get("/api/files/list")
|
|
.with(user("bob"))
|
|
.param("path", "/下载")
|
|
.param("page", "0")
|
|
.param("size", "20"))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.items[0].filename").value("notes.txt"));
|
|
|
|
List<StoredFile> allFiles = storedFileRepository.findAll().stream()
|
|
.filter(file -> !file.isDirectory())
|
|
.sorted(Comparator.comparing(StoredFile::getId))
|
|
.toList();
|
|
assertThat(allFiles).hasSize(2);
|
|
assertThat(allFiles.get(0).getBlob().getId()).isEqualTo(allFiles.get(1).getBlob().getId());
|
|
assertThat(fileBlobRepository.count()).isEqualTo(1L);
|
|
try (var paths = Files.walk(STORAGE_ROOT)) {
|
|
long physicalObjects = paths.filter(Files::isRegularFile).count();
|
|
assertThat(physicalObjects).isEqualTo(1L);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void shouldMoveFileIntoAnotherDirectoryThroughApi() throws Exception {
|
|
User owner = userRepository.findByUsername("alice").orElseThrow();
|
|
|
|
StoredFile downloadDirectory = new StoredFile();
|
|
downloadDirectory.setUser(owner);
|
|
downloadDirectory.setFilename("下载");
|
|
downloadDirectory.setPath("/");
|
|
downloadDirectory.setContentType("directory");
|
|
downloadDirectory.setSize(0L);
|
|
downloadDirectory.setDirectory(true);
|
|
storedFileRepository.save(downloadDirectory);
|
|
Files.createDirectories(STORAGE_ROOT.resolve(owner.getId().toString()).resolve("下载"));
|
|
|
|
mockMvc.perform(patch("/api/files/{fileId}/move", sharedFileId)
|
|
.with(user("alice"))
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content("""
|
|
{
|
|
"path": "/下载"
|
|
}
|
|
"""))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.filename").value("notes.txt"))
|
|
.andExpect(jsonPath("$.data.path").value("/下载"));
|
|
|
|
mockMvc.perform(get("/api/files/list")
|
|
.with(user("alice"))
|
|
.param("path", "/下载")
|
|
.param("page", "0")
|
|
.param("size", "20"))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.items[0].filename").value("notes.txt"));
|
|
}
|
|
|
|
@Test
|
|
void shouldCopyFileIntoAnotherDirectoryThroughApi() throws Exception {
|
|
User owner = userRepository.findByUsername("alice").orElseThrow();
|
|
|
|
StoredFile downloadDirectory = new StoredFile();
|
|
downloadDirectory.setUser(owner);
|
|
downloadDirectory.setFilename("下载");
|
|
downloadDirectory.setPath("/");
|
|
downloadDirectory.setContentType("directory");
|
|
downloadDirectory.setSize(0L);
|
|
downloadDirectory.setDirectory(true);
|
|
storedFileRepository.save(downloadDirectory);
|
|
Files.createDirectories(STORAGE_ROOT.resolve(owner.getId().toString()).resolve("下载"));
|
|
|
|
mockMvc.perform(post("/api/files/{fileId}/copy", sharedFileId)
|
|
.with(user("alice"))
|
|
.contentType(MediaType.APPLICATION_JSON)
|
|
.content("""
|
|
{
|
|
"path": "/下载"
|
|
}
|
|
"""))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.filename").value("notes.txt"))
|
|
.andExpect(jsonPath("$.data.path").value("/下载"));
|
|
|
|
mockMvc.perform(get("/api/files/list")
|
|
.with(user("alice"))
|
|
.param("path", "/下载")
|
|
.param("page", "0")
|
|
.param("size", "20"))
|
|
.andExpect(status().isOk())
|
|
.andExpect(jsonPath("$.data.items[0].filename").value("notes.txt"));
|
|
|
|
List<StoredFile> allFiles = storedFileRepository.findAll().stream()
|
|
.filter(file -> !file.isDirectory())
|
|
.sorted(Comparator.comparing(StoredFile::getId))
|
|
.toList();
|
|
assertThat(allFiles).hasSize(2);
|
|
assertThat(allFiles.get(0).getBlob().getId()).isEqualTo(allFiles.get(1).getBlob().getId());
|
|
assertThat(fileBlobRepository.count()).isEqualTo(1L);
|
|
}
|
|
}
|