task-153: fileKey 兜底查找契约(保存即登记、重启后目录枚举重建、缺失明确失败、清理后不可找、语义稳定)+ 8 条测试
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
package com.nanri.aiimage.modules.file.service;
|
||||
|
||||
import com.nanri.aiimage.config.StorageProperties;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* task-153:fileKey 兜底查找契约(plan 09)。
|
||||
* 上传记录登记内存索引;进程重启后索引为空,按 fileKey 目录枚举兜底重建;
|
||||
* 找不到时明确返回 null(调用方明确失败),不静默。
|
||||
*/
|
||||
class LocalFileKeyLookupTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
private LocalFileStorageService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
StorageProperties properties = new StorageProperties();
|
||||
properties.setLocalTempDir(tempDir.toString());
|
||||
service = new LocalFileStorageService(properties);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileKeyFoundAfterSave() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "a.xlsx", "application/octet-stream", new byte[]{1, 2}), "upload");
|
||||
|
||||
File found = service.findLocalSourceFile(vo.getFileKey());
|
||||
|
||||
assertNotNull(found, "保存后按 fileKey 必须找到");
|
||||
assertTrue(found.isFile());
|
||||
assertEquals(vo.getFileKey(), found.getName().substring(0, vo.getFileKey().length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fileKeyFoundAfterRestart() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "b.xlsx", "application/octet-stream", new byte[]{3}), "upload");
|
||||
// 模拟重启:新实例(内存索引为空)→ 目录枚举兜底
|
||||
StorageProperties properties = new StorageProperties();
|
||||
properties.setLocalTempDir(tempDir.toString());
|
||||
LocalFileStorageService restarted = new LocalFileStorageService(properties);
|
||||
|
||||
File found = restarted.findLocalSourceFile(vo.getFileKey());
|
||||
|
||||
assertNotNull(found, "重启后按 fileKey 目录枚举兜底必须找到");
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingFileKeyFailsExplicitly() {
|
||||
assertNull(service.findLocalSourceFile("0123456789abcdef0123456789abcdef"),
|
||||
"不存在的 fileKey 明确返回 null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullAndBlankFileKeyAreSafe() {
|
||||
assertNull(service.findLocalSourceFile(null));
|
||||
assertNull(service.findLocalSourceFile(" "));
|
||||
assertNull(service.findLocalSourceFile(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedFileKeyNotFindable() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "c.xlsx", "application/octet-stream", new byte[]{4}), "upload");
|
||||
File found = service.findLocalSourceFile(vo.getFileKey());
|
||||
assertNotNull(found);
|
||||
assertTrue(found.delete(), "清理测试文件");
|
||||
|
||||
assertNull(service.findLocalSourceFile(vo.getFileKey()), "文件清理后不可再找到");
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexRecordPersistsForLookup() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "d.xlsx", "application/octet-stream", new byte[]{5}), "upload");
|
||||
|
||||
File found = service.findLocalSourceFile(vo.getFileKey());
|
||||
|
||||
assertNotNull(found);
|
||||
assertEquals(vo.getLocalPath(), found.getAbsolutePath(), "索引命中返回登记路径");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void indexRebuiltByDirectoryEnumeration() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "e.xlsx", "application/octet-stream", new byte[]{6}), "upload");
|
||||
// 清空内存索引(模拟索引丢失),目录枚举仍可重建
|
||||
((java.util.Map<String, String>) org.springframework.test.util.ReflectionTestUtils
|
||||
.getField(service, "sourceFileIndex")).clear();
|
||||
|
||||
File found = service.findLocalSourceFile(vo.getFileKey());
|
||||
|
||||
assertNotNull(found, "索引清空后目录枚举兜底重建");
|
||||
}
|
||||
|
||||
@Test
|
||||
void semanticsFrozen() throws Exception {
|
||||
var vo = service.saveTempFile(new MockMultipartFile("f", "f.xlsx", "application/octet-stream", new byte[]{7}), "upload");
|
||||
File found = service.findLocalSourceFile(vo.getFileKey());
|
||||
File restartFound = new LocalFileStorageService(properties()).findLocalSourceFile(vo.getFileKey());
|
||||
|
||||
assertEquals(found.getAbsolutePath(), restartFound.getAbsolutePath(),
|
||||
"同 fileKey 在重启前后解析到同一文件(语义不变)");
|
||||
}
|
||||
|
||||
private StorageProperties properties() {
|
||||
StorageProperties properties = new StorageProperties();
|
||||
properties.setLocalTempDir(tempDir.toString());
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user