task-152: 清理失败吞异常记指标(aiimage.temp-cleanup.failed 计数、日志+指标均不阻断主流程、按路径隔离、无重试循环)+ 8 条测试

This commit is contained in:
2026-09-02 06:29:59 +08:00
parent f0e12b9b1b
commit 3b34ec3bef
2 changed files with 144 additions and 1 deletions
@@ -0,0 +1,119 @@
package com.nanri.aiimage.modules.file.service;
import com.nanri.aiimage.config.StorageProperties;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
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 org.springframework.test.util.ReflectionTestUtils;
import java.io.File;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* task-152:清理失败吞异常记指标契约(plan 09)。
* 清理失败:记录日志+指标(清理失败计数)、不抛异常、不自动重试成循环;
* 单个条目失败不影响其他条目(隔离)。
*/
@ExtendWith(MockitoExtension.class)
class CleanupFailureMetricTest {
@Mock private StorageProperties storageProperties;
@Mock private MeterRegistry meterRegistry;
private LocalTempCleanupService service;
@BeforeEach
void setUp() {
service = new LocalTempCleanupService(storageProperties);
ReflectionTestUtils.setField(service, "meterRegistry", meterRegistry);
}
@Test
void failureIsLoggedWithoutThrowing() {
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom"));
service.handleCleanupFailure(new File("target/tmp/b.tmp"), null);
}
@Test
void failureRecordsMetric() {
Counter counter = mock(Counter.class);
when(meterRegistry.counter(anyString(), anyString(), anyString())).thenReturn(counter);
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom"));
verify(meterRegistry).counter(eq("aiimage.temp-cleanup.failed"), eq("path"), eq("a.tmp"));
verify(counter).increment();
}
@Test
void failureDoesNotThrowToCaller() {
Counter counter = mock(Counter.class);
when(meterRegistry.counter(anyString(), anyString(), anyString())).thenThrow(
new RuntimeException("metrics down"));
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom"));
}
@Test
void failureMetricNotRepeatedInLoop() {
Counter counter = mock(Counter.class);
when(meterRegistry.counter(anyString(), anyString(), anyString())).thenReturn(counter);
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom"));
verify(counter, times(1)).increment();
}
@Test
void nextRoundCanRetryIndependently() {
Counter counter = mock(Counter.class);
when(meterRegistry.counter(anyString(), anyString(), anyString())).thenReturn(counter);
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom"));
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("boom again"));
verify(counter, times(2)).increment();
}
@Test
void partialFailureIsIsolatedPerPath() {
Counter counterA = mock(Counter.class);
Counter counterB = mock(Counter.class);
when(meterRegistry.counter(anyString(), eq("path"), eq("a.tmp"))).thenReturn(counterA);
when(meterRegistry.counter(anyString(), eq("path"), eq("b.tmp"))).thenReturn(counterB);
service.handleCleanupFailure(new File("target/tmp/a.tmp"), new RuntimeException("x"));
service.handleCleanupFailure(new File("target/tmp/b.tmp"), new RuntimeException("y"));
service.handleCleanupFailure(new File("target/tmp/b.tmp"), new RuntimeException("y2"));
verify(counterA, times(1)).increment();
verify(counterB, times(2)).increment();
}
@Test
void metricTaggedByFileName() {
Counter counter = mock(Counter.class);
when(meterRegistry.counter(anyString(), anyString(), anyString())).thenReturn(counter);
service.handleCleanupFailure(new File("target/tmp/result/x.xlsx"), new RuntimeException("boom"));
verify(meterRegistry).counter(eq("aiimage.temp-cleanup.failed"), eq("path"), eq("x.xlsx"));
}
@Test
void nullChildIsSafe() {
service.handleCleanupFailure(null, new RuntimeException("boom"));
service.handleCleanupFailure(null, null);
}
}