feat(files): add file entity migration
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package com.yoyuzh.files;
|
||||
|
||||
import com.yoyuzh.auth.User;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FileEntityBackfillServiceTest {
|
||||
|
||||
@Mock
|
||||
private StoredFileRepository storedFileRepository;
|
||||
@Mock
|
||||
private FileEntityRepository fileEntityRepository;
|
||||
@Mock
|
||||
private StoredFileEntityRepository storedFileEntityRepository;
|
||||
|
||||
private FileEntityBackfillService backfillService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
backfillService = new FileEntityBackfillService(
|
||||
storedFileRepository,
|
||||
fileEntityRepository,
|
||||
storedFileEntityRepository
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldBackfillPrimaryEntityFromExistingBlob() {
|
||||
StoredFile storedFile = createStoredFile(10L, 7L, "notes.txt", createBlob(20L, "blobs/blob-20"));
|
||||
when(storedFileRepository.findAllByDirectoryFalseAndBlobIsNotNullAndPrimaryEntityIsNull())
|
||||
.thenReturn(List.of(storedFile));
|
||||
when(fileEntityRepository.findByObjectKeyAndEntityType("blobs/blob-20", FileEntityType.VERSION))
|
||||
.thenReturn(Optional.empty());
|
||||
when(fileEntityRepository.save(any(FileEntity.class))).thenAnswer(invocation -> {
|
||||
FileEntity entity = invocation.getArgument(0);
|
||||
entity.setId(100L);
|
||||
return entity;
|
||||
});
|
||||
|
||||
backfillService.backfillPrimaryEntities();
|
||||
|
||||
assertThat(storedFile.getPrimaryEntity()).isNotNull();
|
||||
assertThat(storedFile.getPrimaryEntity().getObjectKey()).isEqualTo("blobs/blob-20");
|
||||
assertThat(storedFile.getPrimaryEntity().getEntityType()).isEqualTo(FileEntityType.VERSION);
|
||||
assertThat(storedFile.getPrimaryEntity().getReferenceCount()).isEqualTo(1);
|
||||
verify(fileEntityRepository).save(any(FileEntity.class));
|
||||
verify(storedFileRepository).save(storedFile);
|
||||
verify(storedFileEntityRepository).save(any(StoredFileEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReuseExistingFileEntityWhenBackfillRunsAgain() {
|
||||
StoredFile storedFile = createStoredFile(11L, 8L, "report.pdf", createBlob(21L, "blobs/blob-21"));
|
||||
FileEntity existingEntity = new FileEntity();
|
||||
existingEntity.setId(101L);
|
||||
existingEntity.setObjectKey("blobs/blob-21");
|
||||
existingEntity.setEntityType(FileEntityType.VERSION);
|
||||
existingEntity.setReferenceCount(3);
|
||||
when(storedFileRepository.findAllByDirectoryFalseAndBlobIsNotNullAndPrimaryEntityIsNull())
|
||||
.thenReturn(List.of(storedFile));
|
||||
when(fileEntityRepository.findByObjectKeyAndEntityType("blobs/blob-21", FileEntityType.VERSION))
|
||||
.thenReturn(Optional.of(existingEntity));
|
||||
|
||||
backfillService.backfillPrimaryEntities();
|
||||
|
||||
assertThat(storedFile.getPrimaryEntity()).isSameAs(existingEntity);
|
||||
assertThat(existingEntity.getReferenceCount()).isEqualTo(4);
|
||||
verify(fileEntityRepository).save(existingEntity);
|
||||
verify(storedFileRepository).save(storedFile);
|
||||
verify(storedFileEntityRepository).save(any(StoredFileEntity.class));
|
||||
}
|
||||
|
||||
private StoredFile createStoredFile(Long id, Long userId, String filename, FileBlob blob) {
|
||||
User user = new User();
|
||||
user.setId(userId);
|
||||
user.setUsername("user-" + userId);
|
||||
|
||||
StoredFile file = new StoredFile();
|
||||
file.setId(id);
|
||||
file.setUser(user);
|
||||
file.setPath("/docs");
|
||||
file.setFilename(filename);
|
||||
file.setBlob(blob);
|
||||
file.setContentType(blob.getContentType());
|
||||
file.setSize(blob.getSize());
|
||||
file.setDirectory(false);
|
||||
file.setCreatedAt(LocalDateTime.now());
|
||||
return file;
|
||||
}
|
||||
|
||||
private FileBlob createBlob(Long id, String objectKey) {
|
||||
FileBlob blob = new FileBlob();
|
||||
blob.setId(id);
|
||||
blob.setObjectKey(objectKey);
|
||||
blob.setContentType("text/plain");
|
||||
blob.setSize(5L);
|
||||
blob.setCreatedAt(LocalDateTime.now());
|
||||
return blob;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.AdditionalMatchers.aryEq;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentCaptor.forClass;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -50,6 +51,10 @@ class FileServiceTest {
|
||||
|
||||
@Mock
|
||||
private FileBlobRepository fileBlobRepository;
|
||||
@Mock
|
||||
private FileEntityRepository fileEntityRepository;
|
||||
@Mock
|
||||
private StoredFileEntityRepository storedFileEntityRepository;
|
||||
|
||||
@Mock
|
||||
private FileContentStorage fileContentStorage;
|
||||
@@ -104,6 +109,86 @@ class FileServiceTest {
|
||||
&& "text/plain".equals(blob.getContentType())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAttachPrimaryEntityWhenUploadingFile() {
|
||||
User user = createUser(7L);
|
||||
MockMultipartFile multipartFile = new MockMultipartFile(
|
||||
"file", "notes.txt", "text/plain", "hello".getBytes());
|
||||
when(storedFileRepository.existsByUserIdAndPathAndFilename(7L, "/docs", "notes.txt")).thenReturn(false);
|
||||
when(fileBlobRepository.save(any(FileBlob.class))).thenAnswer(invocation -> {
|
||||
FileBlob blob = invocation.getArgument(0);
|
||||
blob.setId(100L);
|
||||
return blob;
|
||||
});
|
||||
when(storedFileRepository.save(any(StoredFile.class))).thenAnswer(invocation -> {
|
||||
StoredFile file = invocation.getArgument(0);
|
||||
file.setId(10L);
|
||||
return file;
|
||||
});
|
||||
|
||||
fileService.upload(user, "/docs", multipartFile);
|
||||
|
||||
var savedFileCaptor = forClass(StoredFile.class);
|
||||
verify(storedFileRepository, times(2)).save(savedFileCaptor.capture());
|
||||
StoredFile storedFile = savedFileCaptor.getAllValues().stream()
|
||||
.filter(file -> !file.isDirectory())
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
assertThat(storedFile.getPrimaryEntity()).isNotNull();
|
||||
assertThat(storedFile.getPrimaryEntity().getObjectKey()).isEqualTo(storedFile.getBlob().getObjectKey());
|
||||
assertThat(storedFile.getPrimaryEntity().getEntityType()).isEqualTo(FileEntityType.VERSION);
|
||||
assertThat(storedFile.getPrimaryEntity().getReferenceCount()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPersistFileEntityAndRelationWhenUploadingFile() {
|
||||
fileService = new FileService(
|
||||
storedFileRepository,
|
||||
fileBlobRepository,
|
||||
fileEntityRepository,
|
||||
storedFileEntityRepository,
|
||||
fileContentStorage,
|
||||
fileShareLinkRepository,
|
||||
adminMetricsService,
|
||||
new FileStorageProperties()
|
||||
);
|
||||
User user = createUser(7L);
|
||||
MockMultipartFile multipartFile = new MockMultipartFile(
|
||||
"file", "notes.txt", "text/plain", "hello".getBytes());
|
||||
when(storedFileRepository.existsByUserIdAndPathAndFilename(7L, "/docs", "notes.txt")).thenReturn(false);
|
||||
when(fileBlobRepository.save(any(FileBlob.class))).thenAnswer(invocation -> {
|
||||
FileBlob blob = invocation.getArgument(0);
|
||||
blob.setId(100L);
|
||||
return blob;
|
||||
});
|
||||
when(fileEntityRepository.findByObjectKeyAndEntityType(org.mockito.ArgumentMatchers.anyString(), eq(FileEntityType.VERSION)))
|
||||
.thenReturn(Optional.empty());
|
||||
when(fileEntityRepository.save(any(FileEntity.class))).thenAnswer(invocation -> {
|
||||
FileEntity entity = invocation.getArgument(0);
|
||||
entity.setId(200L);
|
||||
return entity;
|
||||
});
|
||||
when(storedFileRepository.save(any(StoredFile.class))).thenAnswer(invocation -> {
|
||||
StoredFile file = invocation.getArgument(0);
|
||||
file.setId(10L);
|
||||
return file;
|
||||
});
|
||||
|
||||
fileService.upload(user, "/docs", multipartFile);
|
||||
|
||||
var entityCaptor = forClass(FileEntity.class);
|
||||
verify(fileEntityRepository).save(entityCaptor.capture());
|
||||
assertThat(entityCaptor.getValue().getObjectKey()).startsWith("blobs/");
|
||||
assertThat(entityCaptor.getValue().getEntityType()).isEqualTo(FileEntityType.VERSION);
|
||||
assertThat(entityCaptor.getValue().getCreatedBy()).isSameAs(user);
|
||||
|
||||
var relationCaptor = forClass(StoredFileEntity.class);
|
||||
verify(storedFileEntityRepository).save(relationCaptor.capture());
|
||||
assertThat(relationCaptor.getValue().getStoredFile().getId()).isEqualTo(10L);
|
||||
assertThat(relationCaptor.getValue().getFileEntity().getId()).isEqualTo(200L);
|
||||
assertThat(relationCaptor.getValue().getEntityRole()).isEqualTo("PRIMARY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInitiateDirectUploadThroughStorage() {
|
||||
User user = createUser(7L);
|
||||
@@ -323,6 +408,10 @@ class FileServiceTest {
|
||||
assertThat(response.id()).isEqualTo(20L);
|
||||
assertThat(response.path()).isEqualTo("/下载");
|
||||
assertThat(file.getBlob()).isSameAs(blob);
|
||||
var copiedFileCaptor = forClass(StoredFile.class);
|
||||
verify(storedFileRepository).save(copiedFileCaptor.capture());
|
||||
assertThat(copiedFileCaptor.getValue().getPrimaryEntity()).isNotNull();
|
||||
assertThat(copiedFileCaptor.getValue().getPrimaryEntity().getObjectKey()).isEqualTo(blob.getObjectKey());
|
||||
verify(fileContentStorage, never()).copyFile(any(), any(), any(), any());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user