task-161: 巡检任务开关(aiimage.inspection.enabled 默认 disabled、cron 调度、分布式锁双实例去重、单巡检异常隔离、limit 透传)+ 8 条测试

This commit is contained in:
2026-09-02 07:19:56 +08:00
parent 9712bdf538
commit 035ec9ac79
3 changed files with 293 additions and 0 deletions
@@ -0,0 +1,188 @@
package com.nanri.aiimage.modules.task.service;
import com.nanri.aiimage.common.service.DistributedJobLockService;
import com.nanri.aiimage.config.InspectionProperties;
import com.nanri.aiimage.config.StorageProperties;
import com.nanri.aiimage.modules.file.service.TempOrphanInspector;
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.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* task-161:巡检任务开关契约(plan 09)。
* 默认 disabled 不执行;启用后执行并持分布式锁(双实例不重复);单个巡检异常
* 不阻断其余;limit 传入各巡检。
*/
@ExtendWith(MockitoExtension.class)
class InspectionSchedulerTest {
@Mock private DistributedJobLockService distributedJobLockService;
@Mock private StorageProperties storageProperties;
@Mock private TempOrphanInspector tempOrphanInspector;
@Mock private OrphanJobInspector orphanJobInspector;
@Mock private TaskResultMissingInspector taskResultMissingInspector;
@Mock private ResultFileMissingInspector resultFileMissingInspector;
@Mock private CompletedTaskActiveJobInspector completedTaskActiveJobInspector;
private InspectionProperties properties;
private InspectionScheduler scheduler;
@BeforeEach
void setUp() {
properties = new InspectionProperties();
scheduler = new InspectionScheduler(properties, distributedJobLockService, storageProperties,
tempOrphanInspector, orphanJobInspector, taskResultMissingInspector,
resultFileMissingInspector, completedTaskActiveJobInspector);
}
@Test
void disabledByDefaultDoesNotRun() {
assertFalse(properties.isEnabled(), "默认 disabled");
scheduler.runInspections();
verify(orphanJobInspector, never()).inspectOrphanJobs(anyInt());
verify(distributedJobLockService, never()).tryLock(any(), any());
}
@Test
void enabledRunsInspections() {
properties.setEnabled(true);
when(distributedJobLockService.tryLock(any(), any()))
.thenReturn(mock(DistributedJobLockService.LockHandle.class));
when(orphanJobInspector.inspectOrphanJobs(anyInt())).thenReturn(
new OrphanJobInspector.OrphanJobReport(java.util.List.of()));
when(taskResultMissingInspector.inspectTasksMissingResult(anyInt())).thenReturn(
new TaskResultMissingInspector.MissingResultReport(java.util.List.of()));
when(resultFileMissingInspector.inspectResultsMissingFile(anyInt())).thenReturn(
new ResultFileMissingInspector.MissingFileReport(java.util.List.of()));
when(completedTaskActiveJobInspector.inspectTerminalTasksWithActiveJobs(anyInt())).thenReturn(
new CompletedTaskActiveJobInspector.ActiveJobReport(java.util.List.of()));
when(storageProperties.getLocalTempDir()).thenReturn("target/nonexistent-inspection-dir");
scheduler.runInspections();
verify(orphanJobInspector).inspectOrphanJobs(200);
verify(taskResultMissingInspector).inspectTasksMissingResult(200);
verify(resultFileMissingInspector).inspectResultsMissingFile(200);
verify(completedTaskActiveJobInspector).inspectTerminalTasksWithActiveJobs(200);
}
@Test
void scheduleIntervalConfigured() {
assertEquals("0 0 3 * * *", properties.getCron(), "默认调度 cron");
properties.setCron("0 */30 * * * *");
assertEquals("0 */30 * * * *", properties.getCron(), "cron 可配置");
}
@Test
void dualInstanceSkipsWhenLockBusy() {
properties.setEnabled(true);
when(distributedJobLockService.tryLock(any(), any())).thenReturn(null);
scheduler.runInspections();
verify(orphanJobInspector, never()).inspectOrphanJobs(anyInt());
}
@Test
void oneInspectionFailureDoesNotBlockOthers() {
properties.setEnabled(true);
when(distributedJobLockService.tryLock(any(), any()))
.thenReturn(mock(DistributedJobLockService.LockHandle.class));
doThrow(new RuntimeException("orphan job inspector down"))
.when(orphanJobInspector).inspectOrphanJobs(anyInt());
when(taskResultMissingInspector.inspectTasksMissingResult(anyInt())).thenReturn(
new TaskResultMissingInspector.MissingResultReport(java.util.List.of()));
when(resultFileMissingInspector.inspectResultsMissingFile(anyInt())).thenReturn(
new ResultFileMissingInspector.MissingFileReport(java.util.List.of()));
when(completedTaskActiveJobInspector.inspectTerminalTasksWithActiveJobs(anyInt())).thenReturn(
new CompletedTaskActiveJobInspector.ActiveJobReport(java.util.List.of()));
when(storageProperties.getLocalTempDir()).thenReturn("target/nonexistent-inspection-dir");
scheduler.runInspections();
verify(taskResultMissingInspector).inspectTasksMissingResult(200);
verify(resultFileMissingInspector).inspectResultsMissingFile(200);
verify(completedTaskActiveJobInspector).inspectTerminalTasksWithActiveJobs(200);
}
@Test
void limitIsApplied() {
properties.setEnabled(true);
properties.setLimit(50);
when(distributedJobLockService.tryLock(any(), any()))
.thenReturn(mock(DistributedJobLockService.LockHandle.class));
when(orphanJobInspector.inspectOrphanJobs(anyInt())).thenReturn(
new OrphanJobInspector.OrphanJobReport(java.util.List.of()));
when(taskResultMissingInspector.inspectTasksMissingResult(anyInt())).thenReturn(
new TaskResultMissingInspector.MissingResultReport(java.util.List.of()));
when(resultFileMissingInspector.inspectResultsMissingFile(anyInt())).thenReturn(
new ResultFileMissingInspector.MissingFileReport(java.util.List.of()));
when(completedTaskActiveJobInspector.inspectTerminalTasksWithActiveJobs(anyInt())).thenReturn(
new CompletedTaskActiveJobInspector.ActiveJobReport(java.util.List.of()));
when(storageProperties.getLocalTempDir()).thenReturn("target/nonexistent-inspection-dir");
scheduler.runInspections();
verify(orphanJobInspector).inspectOrphanJobs(50);
verify(taskResultMissingInspector).inspectTasksMissingResult(50);
}
@Test
void lockAcquiredWithTtl() {
properties.setEnabled(true);
when(distributedJobLockService.tryLock("temp-file-inspection", Duration.ofMinutes(10)))
.thenReturn(mock(DistributedJobLockService.LockHandle.class));
when(orphanJobInspector.inspectOrphanJobs(anyInt())).thenReturn(
new OrphanJobInspector.OrphanJobReport(java.util.List.of()));
when(taskResultMissingInspector.inspectTasksMissingResult(anyInt())).thenReturn(
new TaskResultMissingInspector.MissingResultReport(java.util.List.of()));
when(resultFileMissingInspector.inspectResultsMissingFile(anyInt())).thenReturn(
new ResultFileMissingInspector.MissingFileReport(java.util.List.of()));
when(completedTaskActiveJobInspector.inspectTerminalTasksWithActiveJobs(anyInt())).thenReturn(
new CompletedTaskActiveJobInspector.ActiveJobReport(java.util.List.of()));
when(storageProperties.getLocalTempDir()).thenReturn("target/nonexistent-inspection-dir");
scheduler.runInspections();
verify(distributedJobLockService).tryLock("temp-file-inspection", Duration.ofMinutes(10));
}
@Test
void rerunIsSafe() {
properties.setEnabled(true);
when(distributedJobLockService.tryLock(any(), any()))
.thenReturn(mock(DistributedJobLockService.LockHandle.class));
when(orphanJobInspector.inspectOrphanJobs(anyInt())).thenReturn(
new OrphanJobInspector.OrphanJobReport(java.util.List.of()));
when(taskResultMissingInspector.inspectTasksMissingResult(anyInt())).thenReturn(
new TaskResultMissingInspector.MissingResultReport(java.util.List.of()));
when(resultFileMissingInspector.inspectResultsMissingFile(anyInt())).thenReturn(
new ResultFileMissingInspector.MissingFileReport(java.util.List.of()));
when(completedTaskActiveJobInspector.inspectTerminalTasksWithActiveJobs(anyInt())).thenReturn(
new CompletedTaskActiveJobInspector.ActiveJobReport(java.util.List.of()));
when(storageProperties.getLocalTempDir()).thenReturn("target/nonexistent-inspection-dir");
scheduler.runInspections();
scheduler.runInspections();
verify(orphanJobInspector, org.mockito.Mockito.times(2)).inspectOrphanJobs(anyInt());
assertTrue(properties.isEnabled());
}
}