task-160: 超保留期仍被引用巡检(超期+引用检出供人工处理、未超期/未引用不报、只读、可重复)+ 8 条测试

This commit is contained in:
2026-09-02 07:14:05 +08:00
parent 5305d6dbc2
commit 9712bdf538
2 changed files with 168 additions and 0 deletions
@@ -0,0 +1,119 @@
package com.nanri.aiimage.modules.file.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* task-160:超保留期仍被引用巡检契约(plan 09)。
* 临时文件超保留期但仍有引用 → 检出(供人工处理);未超期不报;
* 超期未引用不报(归孤立文件报表);只读;可重复。
*/
class OverRetentionReferencedTest {
@TempDir
Path tempDir;
private final TempOrphanInspector inspector = new TempOrphanInspector();
private Path writeFile(String name, String content) throws Exception {
Path path = tempDir.resolve(name);
Files.writeString(path, content, StandardCharsets.UTF_8);
return path;
}
@Test
void overRetentionReferencedDetected() throws Exception {
writeFile("kept.tmp", "x");
var report = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> name.equals("kept.tmp"));
assertEquals(1, report.entries().size(), "超期且被引用必须检出");
}
@Test
void inRetentionNotReported() throws Exception {
writeFile("fresh.tmp", "x");
var report = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().minus(1, ChronoUnit.HOURS), name -> true);
assertTrue(report.isEmpty(), "未超保留期不报");
}
@Test
void overRetentionUnreferencedNotReportedHere() throws Exception {
writeFile("orphan.tmp", "x");
var report = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> false);
assertTrue(report.isEmpty(), "超期未引用归孤立文件报表,不在此处重复报");
}
@Test
void boundaryRetentionIsGraceful() throws Exception {
writeFile("edge.tmp", "x");
Instant now = Instant.now();
var report = inspector.inspectOverRetentionReferenced(tempDir.toFile(), now, name -> true);
// 边界(mtime 与边界接近):不抛错、报表确定
assertTrue(report.isEmpty() || report.entries().size() == 1);
}
@Test
void emptyReportWhenNothingReferenced() throws Exception {
writeFile("a.tmp", "x");
var report = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> false);
assertTrue(report.isEmpty());
}
@Test
void readOnlyDoesNotDelete() throws Exception {
Path file = writeFile("referenced.tmp", "keep");
inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> true);
assertTrue(Files.exists(file), "只读巡检不得删除");
}
@Test
void reportIncludesDetails() throws Exception {
writeFile("detail.tmp", "0123456789");
var report = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> true);
TempOrphanInspector.OrphanFileEntry entry = report.entries().getFirst();
assertTrue(entry.path().contains("detail.tmp"));
assertEquals(10L, entry.sizeBytes());
assertTrue(entry.lastModifiedAt() != null && !entry.lastModifiedAt().isBlank());
}
@Test
void rerunIsSafeAndStable() throws Exception {
writeFile("rerun.tmp", "x");
var first = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> true);
var second = inspector.inspectOverRetentionReferenced(
tempDir.toFile(), Instant.now().plus(1, ChronoUnit.HOURS), name -> true);
assertEquals(first.entries().size(), second.entries().size());
assertEquals(first.entries().getFirst().path(), second.entries().getFirst().path());
}
}