You've already forked FrameTour-BE
fix(pipeline): 增加防御性检查避免空指针异常
- 在多个阶段中增加对 memberSourceList 和 searchResult 的空值检查 - 当 memberSourceList 为空时跳过视频重切和购买状态处理逻辑 - 当 searchResult 为空时跳过人脸补救逻辑 - 增加对自定义匹配场景的判断,非该场景则跳过指标记录 - 为各个阶段添加详细的单元测试覆盖各种边界条件
This commit is contained in:
@@ -54,6 +54,12 @@ public class FaceRecoveryStage extends AbstractFaceMatchingStage<FaceMatchingCon
|
||||
SearchFaceRespVo searchResult = context.getSearchResult();
|
||||
Long faceId = context.getFaceId();
|
||||
|
||||
// 防御性检查:searchResult为空
|
||||
if (searchResult == null) {
|
||||
log.debug("searchResult为空,跳过补救逻辑,faceId={}", faceId);
|
||||
return StageResult.skipped("searchResult为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 执行补救逻辑(补救逻辑内部会判断是否需要触发)
|
||||
SearchFaceRespVo recoveredResult = faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
|
||||
@@ -62,6 +62,12 @@ public class HandleVideoRecreationStage extends AbstractFaceMatchingStage<FaceMa
|
||||
List<Long> sampleListIds = context.getSampleListIds();
|
||||
boolean isNew = context.isNew();
|
||||
|
||||
// 防御性检查:memberSourceList为空
|
||||
if (memberSourceEntityList == null || memberSourceEntityList.isEmpty()) {
|
||||
log.debug("memberSourceList为空,跳过视频重切,faceId={}", faceId);
|
||||
return StageResult.skipped("memberSourceList为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 处理视频重切
|
||||
videoRecreationHandler.handleVideoRecreation(
|
||||
|
||||
@@ -62,6 +62,12 @@ public class ProcessBuyStatusStage extends AbstractFaceMatchingStage<FaceMatchin
|
||||
Long scenicId = context.getFace().getScenicId();
|
||||
Long faceId = context.getFaceId();
|
||||
|
||||
// 防御性检查:memberSourceList为空
|
||||
if (memberSourceEntityList == null || memberSourceEntityList.isEmpty()) {
|
||||
log.debug("memberSourceList为空,跳过购买状态处理,faceId={}", faceId);
|
||||
return StageResult.skipped("memberSourceList为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 处理购买状态
|
||||
buyStatusProcessor.processBuyStatus(
|
||||
|
||||
@@ -61,6 +61,12 @@ public class ProcessFreeSourceStage extends AbstractFaceMatchingStage<FaceMatchi
|
||||
boolean isNew = context.isNew();
|
||||
Long faceId = context.getFaceId();
|
||||
|
||||
// 防御性检查:memberSourceList为空
|
||||
if (memberSourceEntityList == null || memberSourceEntityList.isEmpty()) {
|
||||
log.debug("memberSourceList为空,跳过免费逻辑,faceId={}", faceId);
|
||||
return StageResult.skipped("memberSourceList为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 处理免费逻辑
|
||||
List<Long> freeSourceIds = sourceRelationProcessor.processFreeSourceLogic(
|
||||
|
||||
@@ -51,6 +51,12 @@ public class RecordCustomMatchMetricsStage extends AbstractFaceMatchingStage<Fac
|
||||
protected StageResult<FaceMatchingContext> doExecute(FaceMatchingContext context) {
|
||||
Long faceId = context.getFaceId();
|
||||
|
||||
// 防御性检查:只有自定义匹配场景才执行
|
||||
if (context.getScene() != FaceMatchingScene.CUSTOM_MATCHING) {
|
||||
log.debug("非自定义匹配场景,跳过记录,faceId={}", faceId);
|
||||
return StageResult.skipped("非自定义匹配场景");
|
||||
}
|
||||
|
||||
try {
|
||||
metricsRecorder.recordCustomMatchCount(faceId);
|
||||
log.debug("记录自定义匹配次数: faceId={}", faceId);
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.biz.TaskStatusBiz;
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.service.pc.helper.ScenicConfigFacade;
|
||||
import com.ycwl.basic.service.task.TaskService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* CreateTaskStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CreateTaskStageTest {
|
||||
|
||||
@Mock
|
||||
private ScenicConfigFacade scenicConfigFacade;
|
||||
|
||||
@Mock
|
||||
private TaskService taskService;
|
||||
|
||||
@Mock
|
||||
private TaskStatusBiz taskStatusBiz;
|
||||
|
||||
@InjectMocks
|
||||
private CreateTaskStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AutoCreateTask_Success() {
|
||||
// Given: 配置为自动创建任务(faceSelectFirst = false)
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(false);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("自动创建任务成功"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskService, times(1)).autoCreateTaskByFaceId(1L);
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_WaitForUserSelection_Skipped() {
|
||||
// Given: 配置为等待用户选择(faceSelectFirst = true)
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(true);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
assertTrue(result.getMessage().contains("等待用户手动选择"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
verify(taskService, never()).autoCreateTaskByFaceId(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_CheckConfigFailed_Degraded() {
|
||||
// Given: 查询配置失败
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenThrow(new RuntimeException("Config service error"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(10L);
|
||||
verify(taskService, never()).autoCreateTaskByFaceId(anyLong());
|
||||
verify(taskStatusBiz, never()).setFaceCutStatus(anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AutoCreateTaskFailed_Degraded() {
|
||||
// Given: 自动创建任务失败
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(false);
|
||||
doThrow(new RuntimeException("Task creation error"))
|
||||
.when(taskService).autoCreateTaskByFaceId(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
verify(taskService, times(1)).autoCreateTaskByFaceId(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SetStatusFailed_Degraded() {
|
||||
// Given: 设置状态失败
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(true);
|
||||
doThrow(new RuntimeException("Status set error"))
|
||||
.when(taskStatusBiz).setFaceCutStatus(1L, 2);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
verify(taskStatusBiz, times(1)).setFaceCutStatus(1L, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(999L);
|
||||
|
||||
when(scenicConfigFacade.isFaceSelectFirst(999L))
|
||||
.thenReturn(false);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(scenicConfigFacade, times(1)).isFaceSelectFirst(999L);
|
||||
verify(taskService, times(1)).autoCreateTaskByFaceId(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(888L, true);
|
||||
face.setId(888L);
|
||||
context.setFace(face);
|
||||
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenReturn(false);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(taskService, times(1)).autoCreateTaskByFaceId(888L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
when(scenicConfigFacade.isFaceSelectFirst(10L))
|
||||
.thenThrow(new NullPointerException("Null scenic config"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("任务创建失败"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
import com.ycwl.basic.service.pc.processor.FaceRecoveryStrategy;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* FaceRecoveryStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FaceRecoveryStageTest {
|
||||
|
||||
@Mock
|
||||
private FaceRecoveryStrategy faceRecoveryStrategy;
|
||||
|
||||
@InjectMocks
|
||||
private FaceRecoveryStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
private ScenicConfigManager scenicConfig;
|
||||
private IFaceBodyAdapter faceBodyAdapter;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
scenicConfig = mock(ScenicConfigManager.class);
|
||||
faceBodyAdapter = mock(IFaceBodyAdapter.class);
|
||||
|
||||
context.setFace(face);
|
||||
context.setScenicConfig(scenicConfig);
|
||||
context.setFaceBodyAdapter(faceBodyAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SearchResultNull_Skipped() {
|
||||
// Given: searchResult为null
|
||||
context.setSearchResult(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(faceRecoveryStrategy, never()).executeFaceRecoveryLogic(any(), any(), any(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoRecoveryNeeded_Success() {
|
||||
// Given: 不需要补救,返回原searchResult
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.85f);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, scenicConfig, faceBodyAdapter, 10L))
|
||||
.thenReturn(searchResult); // 返回同一个对象
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("无需补救"));
|
||||
assertEquals(searchResult, context.getSearchResult());
|
||||
verify(faceRecoveryStrategy, times(1))
|
||||
.executeFaceRecoveryLogic(searchResult, scenicConfig, faceBodyAdapter, 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecoveryTriggered_Degraded() {
|
||||
// Given: 触发补救,返回新的searchResult
|
||||
SearchFaceRespVo originalResult = new SearchFaceRespVo();
|
||||
originalResult.setScore(0.50f);
|
||||
context.setSearchResult(originalResult);
|
||||
|
||||
SearchFaceRespVo recoveredResult = new SearchFaceRespVo();
|
||||
recoveredResult.setScore(0.85f);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
originalResult, scenicConfig, faceBodyAdapter, 10L))
|
||||
.thenReturn(recoveredResult); // 返回新对象
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("触发补救逻辑,重新搜索"));
|
||||
assertEquals(recoveredResult, context.getSearchResult());
|
||||
assertNotEquals(originalResult, context.getSearchResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecoveryStrategyFailed_Degraded() {
|
||||
// Given: 补救策略执行失败
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.50f);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, scenicConfig, faceBodyAdapter, 10L))
|
||||
.thenThrow(new RuntimeException("Recovery strategy error"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("补救逻辑执行失败"));
|
||||
verify(faceRecoveryStrategy, times(1))
|
||||
.executeFaceRecoveryLogic(searchResult, scenicConfig, faceBodyAdapter, 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(888L);
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, scenicConfig, faceBodyAdapter, 888L))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(faceRecoveryStrategy, times(1))
|
||||
.executeFaceRecoveryLogic(searchResult, scenicConfig, faceBodyAdapter, 888L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullFaceBodyAdapter() {
|
||||
// Given: faceBodyAdapter为null
|
||||
context.setFaceBodyAdapter(null);
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, scenicConfig, null, 10L))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(faceRecoveryStrategy, times(1))
|
||||
.executeFaceRecoveryLogic(searchResult, scenicConfig, null, 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullScenicConfig() {
|
||||
// Given: scenicConfig为null
|
||||
context.setScenicConfig(null);
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, null, faceBodyAdapter, 10L))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(faceRecoveryStrategy, times(1))
|
||||
.executeFaceRecoveryLogic(searchResult, null, faceBodyAdapter, 10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
when(faceRecoveryStrategy.executeFaceRecoveryLogic(
|
||||
searchResult, scenicConfig, faceBodyAdapter, 10L))
|
||||
.thenThrow(new NullPointerException("Null strategy"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("补救逻辑执行失败"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.face.pipeline.helper.PuzzleGenerationOrchestrator;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* GeneratePuzzleStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GeneratePuzzleStageTest {
|
||||
|
||||
@Mock
|
||||
private PuzzleGenerationOrchestrator puzzleOrchestrator;
|
||||
|
||||
@InjectMocks
|
||||
private GeneratePuzzleStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
face.setFaceUrl("http://example.com/face1.jpg");
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success() {
|
||||
// Given: 正常执行
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("拼图模板已提交异步生成"));
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_EmptyFaceUrl_Success() {
|
||||
// Given: faceUrl为空字符串
|
||||
face.setFaceUrl("");
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullFaceUrl_Success() {
|
||||
// Given: faceUrl为null
|
||||
face.setFaceUrl(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_OrchestratorFailed_Degraded() {
|
||||
// Given: orchestrator执行失败
|
||||
doThrow(new RuntimeException("Orchestrator error"))
|
||||
.when(puzzleOrchestrator).generateAllTemplatesAsync(
|
||||
10L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("提交拼图生成任务失败"));
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(888L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(888L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(777L, true);
|
||||
face.setId(777L);
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 777L, 100L, "http://example.com/face1.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentMemberId() {
|
||||
// Given: 不同的memberId
|
||||
face.setMemberId(999L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 999L, "http://example.com/face1.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceUrl() {
|
||||
// Given: 不同的faceUrl
|
||||
face.setFaceUrl("http://cdn.example.com/face999.jpg");
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, "http://cdn.example.com/face999.jpg");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_LongFaceUrl() {
|
||||
// Given: 很长的faceUrl
|
||||
String longUrl = "http://cdn.example.com/very/long/path/to/face/image/with/many/segments/" +
|
||||
"face_id_1234567890_scenic_10_member_100_timestamp_1234567890.jpg";
|
||||
face.setFaceUrl(longUrl);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, longUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
doThrow(new NullPointerException("Null orchestrator"))
|
||||
.when(puzzleOrchestrator).generateAllTemplatesAsync(
|
||||
anyLong(), anyLong(), anyLong(), anyString());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("提交拼图生成任务失败"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_IllegalArgumentException_Degraded() {
|
||||
// Given: 非法参数异常
|
||||
doThrow(new IllegalArgumentException("Invalid face URL"))
|
||||
.when(puzzleOrchestrator).generateAllTemplatesAsync(
|
||||
10L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("提交拼图生成任务失败"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_OldUser() {
|
||||
// Given: 老用户
|
||||
context = FaceMatchingContext.forAutoMatching(1L, false);
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(puzzleOrchestrator, times(1))
|
||||
.generateAllTemplatesAsync(10L, 1L, 100L, "http://example.com/face1.jpg");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.service.pc.processor.VideoRecreationHandler;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* HandleVideoRecreationStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HandleVideoRecreationStageTest {
|
||||
|
||||
@Mock
|
||||
private VideoRecreationHandler videoRecreationHandler;
|
||||
|
||||
@InjectMocks
|
||||
private HandleVideoRecreationStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListNull_Skipped() {
|
||||
// Given: memberSourceList为null
|
||||
context.setMemberSourceList(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(videoRecreationHandler, never()).handleVideoRecreation(
|
||||
anyLong(), anyList(), anyLong(), anyLong(), anyList(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListEmpty_Skipped() {
|
||||
// Given: memberSourceList为空
|
||||
context.setMemberSourceList(new ArrayList<>());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(videoRecreationHandler, never()).handleVideoRecreation(
|
||||
anyLong(), anyList(), anyLong(), anyLong(), anyList(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_WithSampleListIds() {
|
||||
// Given: 有memberSourceList和sampleListIds
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L, 102L, 103L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("视频重切处理完成"));
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_NullSampleListIds() {
|
||||
// Given: sampleListIds为null
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
context.setSampleListIds(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_EmptySampleListIds() {
|
||||
// Given: sampleListIds为空
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
context.setSampleListIds(new ArrayList<>());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, new ArrayList<>(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_OldUser() {
|
||||
// Given: 老用户
|
||||
context = FaceMatchingContext.forAutoMatching(1L, false);
|
||||
context.setFace(face);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, sampleListIds, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_HandlerFailed_Degraded() {
|
||||
// Given: 处理器执行失败
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
doThrow(new RuntimeException("Handler error"))
|
||||
.when(videoRecreationHandler).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, sampleListIds, true);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("视频重切处理失败"));
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(888L);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
888L, memberSourceList, 1L, 100L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(777L, true);
|
||||
face.setId(777L);
|
||||
context.setFace(face);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 777L, 100L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentMemberId() {
|
||||
// Given: 不同的memberId
|
||||
face.setMemberId(999L);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 999L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ManySources() {
|
||||
// Given: 大量源文件
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(20);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> sampleListIds = Arrays.asList(101L, 102L, 103L, 104L, 105L);
|
||||
context.setSampleListIds(sampleListIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(videoRecreationHandler, times(1)).handleVideoRecreation(
|
||||
10L, memberSourceList, 1L, 100L, sampleListIds, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
doThrow(new NullPointerException("Null handler"))
|
||||
.when(videoRecreationHandler).handleVideoRecreation(
|
||||
anyLong(), anyList(), anyLong(), anyLong(), anyList(), anyBoolean());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("视频重切处理失败"));
|
||||
}
|
||||
|
||||
private List<MemberSourceEntity> createMemberSourceList(int count) {
|
||||
List<MemberSourceEntity> list = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MemberSourceEntity entity = new MemberSourceEntity();
|
||||
entity.setMemberId(100L);
|
||||
entity.setSourceId((long) (i + 1));
|
||||
list.add(entity);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.service.pc.processor.BuyStatusProcessor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyList;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* ProcessBuyStatusStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ProcessBuyStatusStageTest {
|
||||
|
||||
@Mock
|
||||
private BuyStatusProcessor buyStatusProcessor;
|
||||
|
||||
@InjectMocks
|
||||
private ProcessBuyStatusStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListNull_Skipped() {
|
||||
// Given: memberSourceList为null
|
||||
context.setMemberSourceList(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(buyStatusProcessor, never()).processBuyStatus(anyList(), anyList(), anyLong(), anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListEmpty_Skipped() {
|
||||
// Given: memberSourceList为空
|
||||
context.setMemberSourceList(new ArrayList<>());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(buyStatusProcessor, never()).processBuyStatus(anyList(), anyList(), anyLong(), anyLong(), anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_WithFreeSource() {
|
||||
// Given: 有免费源文件
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L, 2L, 3L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("购买状态处理完成"));
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_NoFreeSource() {
|
||||
// Given: 没有免费源文件(freeSourceIds为空)
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = new ArrayList<>();
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_NullFreeSource() {
|
||||
// Given: freeSourceIds为null
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
context.setFreeSourceIds(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, null, 100L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ProcessorFailed_Degraded() {
|
||||
// Given: 处理器执行失败
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
doThrow(new RuntimeException("Processor error"))
|
||||
.when(buyStatusProcessor).processBuyStatus(
|
||||
memberSourceList, freeSourceIds, 100L, 10L, 1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("购买状态处理失败"));
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentMemberId() {
|
||||
// Given: 不同的memberId
|
||||
face.setMemberId(999L);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 999L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(888L);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 888L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(777L, true);
|
||||
face.setId(777L);
|
||||
context.setFace(face);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 10L, 777L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AllSourcesFree() {
|
||||
// Given: 所有源文件都免费
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);
|
||||
context.setFreeSourceIds(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(buyStatusProcessor, times(1))
|
||||
.processBuyStatus(memberSourceList, freeSourceIds, 100L, 10L, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
doThrow(new NullPointerException("Null processor"))
|
||||
.when(buyStatusProcessor).processBuyStatus(
|
||||
anyList(), anyList(), anyLong(), anyLong(), anyLong());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("购买状态处理失败"));
|
||||
}
|
||||
|
||||
private List<MemberSourceEntity> createMemberSourceList(int count) {
|
||||
List<MemberSourceEntity> list = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MemberSourceEntity entity = new MemberSourceEntity();
|
||||
entity.setMemberId(100L);
|
||||
entity.setSourceId((long) (i + 1));
|
||||
list.add(entity);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.service.pc.processor.SourceRelationProcessor;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* ProcessFreeSourceStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ProcessFreeSourceStageTest {
|
||||
|
||||
@Mock
|
||||
private SourceRelationProcessor sourceRelationProcessor;
|
||||
|
||||
@InjectMocks
|
||||
private ProcessFreeSourceStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListNull_Skipped() {
|
||||
// Given: memberSourceList为null
|
||||
context.setMemberSourceList(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(sourceRelationProcessor, never())
|
||||
.processFreeSourceLogic(anyList(), anyLong(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MemberSourceListEmpty_Skipped() {
|
||||
// Given: memberSourceList为空
|
||||
context.setMemberSourceList(new ArrayList<>());
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
verify(sourceRelationProcessor, never())
|
||||
.processFreeSourceLogic(anyList(), anyLong(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_WithFreeSource() {
|
||||
// Given: 有免费源文件
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L, 2L, 3L);
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenReturn(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("确定了3个免费源文件"));
|
||||
assertEquals(freeSourceIds, context.getFreeSourceIds());
|
||||
verify(sourceRelationProcessor, times(1))
|
||||
.processFreeSourceLogic(memberSourceList, 10L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_NoFreeSource() {
|
||||
// Given: 没有免费源文件
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = new ArrayList<>();
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenReturn(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("确定了0个免费源文件"));
|
||||
assertEquals(freeSourceIds, context.getFreeSourceIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success_NullFreeSource() {
|
||||
// Given: 返回null(无免费源文件)
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenReturn(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("确定了0个免费源文件"));
|
||||
assertNull(context.getFreeSourceIds());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_OldUser() {
|
||||
// Given: 老用户
|
||||
context = FaceMatchingContext.forAutoMatching(1L, false);
|
||||
context.setFace(face);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L);
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, false))
|
||||
.thenReturn(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(sourceRelationProcessor, times(1))
|
||||
.processFreeSourceLogic(memberSourceList, 10L, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ProcessorFailed_Degraded() {
|
||||
// Given: 处理器执行失败
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenThrow(new RuntimeException("Processor error"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("免费源文件处理失败"));
|
||||
verify(sourceRelationProcessor, times(1))
|
||||
.processFreeSourceLogic(memberSourceList, 10L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentScenicId() {
|
||||
// Given: 不同的景区ID
|
||||
face.setScenicId(888L);
|
||||
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L, 2L);
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 888L, true))
|
||||
.thenReturn(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(sourceRelationProcessor, times(1))
|
||||
.processFreeSourceLogic(memberSourceList, 888L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AllSourcesFree() {
|
||||
// Given: 所有源文件都免费
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(5);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
List<Long> freeSourceIds = Arrays.asList(1L, 2L, 3L, 4L, 5L);
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenReturn(freeSourceIds);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("确定了5个免费源文件"));
|
||||
assertEquals(5, context.getFreeSourceIds().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
List<MemberSourceEntity> memberSourceList = createMemberSourceList(3);
|
||||
context.setMemberSourceList(memberSourceList);
|
||||
|
||||
when(sourceRelationProcessor.processFreeSourceLogic(memberSourceList, 10L, true))
|
||||
.thenThrow(new NullPointerException("Null processor"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("免费源文件处理失败"));
|
||||
}
|
||||
|
||||
private List<MemberSourceEntity> createMemberSourceList(int count) {
|
||||
List<MemberSourceEntity> list = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
MemberSourceEntity entity = new MemberSourceEntity();
|
||||
entity.setMemberId(100L);
|
||||
entity.setSourceId((long) (i + 1));
|
||||
list.add(entity);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.face.pipeline.enums.FaceMatchingScene;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* RecordCustomMatchMetricsStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RecordCustomMatchMetricsStageTest {
|
||||
|
||||
@Mock
|
||||
private FaceMetricsRecorder metricsRecorder;
|
||||
|
||||
@InjectMocks
|
||||
private RecordCustomMatchMetricsStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_CustomMatchingScene_Success() {
|
||||
// Given: 自定义匹配场景
|
||||
context = FaceMatchingContext.forCustomMatching(1L, Arrays.asList(101L, 102L));
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("自定义匹配指标记录完成"));
|
||||
assertEquals(FaceMatchingScene.CUSTOM_MATCHING, context.getScene());
|
||||
verify(metricsRecorder, times(1)).recordCustomMatchCount(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AutoMatchingScene_Skipped() {
|
||||
// Given: 自动匹配场景
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
assertEquals(FaceMatchingScene.AUTO_MATCHING, context.getScene());
|
||||
verify(metricsRecorder, never()).recordCustomMatchCount(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecordFailed_Degraded() {
|
||||
// Given: 自定义匹配场景,但记录失败
|
||||
context = FaceMatchingContext.forCustomMatching(1L, Arrays.asList(101L, 102L));
|
||||
context.setFace(face);
|
||||
|
||||
doThrow(new RuntimeException("Metrics record error"))
|
||||
.when(metricsRecorder).recordCustomMatchCount(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("指标记录失败"));
|
||||
verify(metricsRecorder, times(1)).recordCustomMatchCount(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forCustomMatching(888L, Arrays.asList(101L, 102L));
|
||||
face.setId(888L);
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(metricsRecorder, times(1)).recordCustomMatchCount(888L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
context = FaceMatchingContext.forCustomMatching(1L, Arrays.asList(101L, 102L));
|
||||
context.setFace(face);
|
||||
|
||||
doThrow(new NullPointerException("Null recorder"))
|
||||
.when(metricsRecorder).recordCustomMatchCount(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("指标记录失败"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MultipleFaceSamples() {
|
||||
// Given: 多个faceSample
|
||||
context = FaceMatchingContext.forCustomMatching(
|
||||
1L,
|
||||
Arrays.asList(101L, 102L, 103L, 104L, 105L));
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(metricsRecorder, times(1)).recordCustomMatchCount(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SingleFaceSample() {
|
||||
// Given: 单个faceSample
|
||||
context = FaceMatchingContext.forCustomMatching(1L, Arrays.asList(101L));
|
||||
context.setFace(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(metricsRecorder, times(1)).recordCustomMatchCount(1L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.face.pipeline.core.FaceMatchingContext;
|
||||
import com.ycwl.basic.face.pipeline.core.StageResult;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* RecordMetricsStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RecordMetricsStageTest {
|
||||
|
||||
@Mock
|
||||
private FaceMetricsRecorder metricsRecorder;
|
||||
|
||||
@InjectMocks
|
||||
private RecordMetricsStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setMemberId(100L);
|
||||
face.setScenicId(10L);
|
||||
|
||||
context.setFace(face);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NoSearchResult_Success() {
|
||||
// Given: searchResult为null
|
||||
context.setSearchResult(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("识别指标记录完成"));
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(1L);
|
||||
verify(metricsRecorder, never()).recordLowThreshold(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SearchResultNotLowThreshold_Success() {
|
||||
// Given: searchResult不为null,但未触发低阈值
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setLowThreshold(false);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(1L);
|
||||
verify(metricsRecorder, never()).recordLowThreshold(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_LowThresholdTriggered_Success() {
|
||||
// Given: searchResult触发低阈值
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setLowThreshold(true);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertTrue(result.getMessage().contains("识别指标记录完成"));
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(1L);
|
||||
verify(metricsRecorder, times(1)).recordLowThreshold(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecordCountFailed_Degraded() {
|
||||
// Given: 记录识别次数失败
|
||||
doThrow(new RuntimeException("Record error"))
|
||||
.when(metricsRecorder).recordRecognitionCount(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("指标记录失败"));
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(1L);
|
||||
verify(metricsRecorder, never()).recordLowThreshold(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecordLowThresholdFailed_Degraded() {
|
||||
// Given: 记录低阈值失败
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setLowThreshold(true);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
doThrow(new RuntimeException("Low threshold record error"))
|
||||
.when(metricsRecorder).recordLowThreshold(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("指标记录失败"));
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(1L);
|
||||
verify(metricsRecorder, times(1)).recordLowThreshold(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DifferentFaceId() {
|
||||
// Given: 不同的faceId
|
||||
context = FaceMatchingContext.forAutoMatching(999L, true);
|
||||
face.setId(999L);
|
||||
context.setFace(face);
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setLowThreshold(true);
|
||||
context.setSearchResult(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(metricsRecorder, times(1)).recordRecognitionCount(999L);
|
||||
verify(metricsRecorder, times(1)).recordLowThreshold(999L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullPointerException_Degraded() {
|
||||
// Given: 空指针异常
|
||||
doThrow(new NullPointerException("Null recorder"))
|
||||
.when(metricsRecorder).recordRecognitionCount(1L);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isDegraded());
|
||||
assertTrue(result.getMessage().contains("指标记录失败"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user