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