You've already forked FrameTour-BE
test(pipeline): 添加人脸匹配流水线单元测试
- 为CustomFaceSearchStage添加完整单元测试覆盖各种匹配模式 - 为人脸识别阶段FaceRecognitionStage编写测试用例 - 为上下文准备阶段PrepareContextStage增加测试验证 - 包含成功、失败、异常等边界情况测试 - 验证不同匹配模式下的结果合并逻辑 - 测试人工选择和自动匹配场景的处理差异
This commit is contained in:
@@ -67,6 +67,12 @@ public class FilterByDevicePhotoLimitStage extends AbstractFaceMatchingStage<Fac
|
||||
List<Long> sampleListIds = context.getSampleListIds();
|
||||
Long faceId = context.getFaceId();
|
||||
|
||||
// 防御性检查:faceSamples为空
|
||||
if (faceSamples == null || faceSamples.isEmpty()) {
|
||||
log.debug("faceSamples为空,跳过设备照片限制筛选,faceId={}", faceId);
|
||||
return StageResult.skipped("faceSamples为空");
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 构建样本ID到实体的映射
|
||||
Map<Long, FaceSampleEntity> sampleMap = faceSamples.stream()
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
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.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
import com.ycwl.basic.service.pc.helper.SearchResultMerger;
|
||||
import com.ycwl.basic.service.task.TaskFaceService;
|
||||
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 java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* CustomFaceSearchStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CustomFaceSearchStageTest {
|
||||
|
||||
@Mock
|
||||
private TaskFaceService taskFaceService;
|
||||
|
||||
@Mock
|
||||
private SearchResultMerger resultMerger;
|
||||
|
||||
@Mock
|
||||
private IFaceBodyAdapter faceBodyAdapter;
|
||||
|
||||
@InjectMocks
|
||||
private CustomFaceSearchStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forCustomMatching(1L, Arrays.asList(101L, 102L));
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setScenicId(10L);
|
||||
face.setFaceUrl("http://example.com/face.jpg");
|
||||
context.setFace(face);
|
||||
context.setFaceBodyAdapter(faceBodyAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Mode2_DirectUse() {
|
||||
// Given: 模式2,直接使用用户选择的样本
|
||||
context.setFaceSelectPostMode(2);
|
||||
context.setFaceSampleIds(Arrays.asList(101L, 102L, 103L));
|
||||
|
||||
SearchFaceRespVo directResult = new SearchFaceRespVo();
|
||||
directResult.setSampleListIds(Arrays.asList(101L, 102L, 103L));
|
||||
|
||||
when(resultMerger.createDirectResult(Arrays.asList(101L, 102L, 103L)))
|
||||
.thenReturn(directResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(directResult, context.getSearchResult());
|
||||
assertEquals(3, context.getSampleListIds().size());
|
||||
verify(resultMerger, times(1)).createDirectResult(anyList());
|
||||
verify(taskFaceService, never()).searchFace(any(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Mode2_WithMatchResult() {
|
||||
// Given: 模式2,保留原始matchResult
|
||||
context.setFaceSelectPostMode(2);
|
||||
context.setFaceSampleIds(Arrays.asList(101L, 102L));
|
||||
face.setMatchResult("{\"score\":0.85}");
|
||||
|
||||
SearchFaceRespVo directResult = new SearchFaceRespVo();
|
||||
directResult.setSampleListIds(Arrays.asList(101L, 102L));
|
||||
|
||||
when(resultMerger.createDirectResult(anyList())).thenReturn(directResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals("{\"score\":0.85}", directResult.getSearchResultJson());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Mode0_Union() throws Exception {
|
||||
// Given: 模式0(并集),搜索多个样本并合并
|
||||
context.setFaceSelectPostMode(0);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
FaceSampleEntity sample2 = createSample(102L, "http://example.com/s2.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1, sample2));
|
||||
context.setFaceSampleIds(Arrays.asList(101L, 102L));
|
||||
|
||||
SearchFaceRespVo searchResult1 = new SearchFaceRespVo();
|
||||
searchResult1.setSampleListIds(Arrays.asList(201L, 202L));
|
||||
|
||||
SearchFaceRespVo searchResult2 = new SearchFaceRespVo();
|
||||
searchResult2.setSampleListIds(Arrays.asList(202L, 203L));
|
||||
|
||||
when(taskFaceService.searchFace(eq(faceBodyAdapter), eq("10"), eq("http://example.com/s1.jpg"), anyString()))
|
||||
.thenReturn(searchResult1);
|
||||
when(taskFaceService.searchFace(eq(faceBodyAdapter), eq("10"), eq("http://example.com/s2.jpg"), anyString()))
|
||||
.thenReturn(searchResult2);
|
||||
|
||||
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
||||
mergedResult.setSampleListIds(Arrays.asList(201L, 202L, 203L)); // 并集
|
||||
|
||||
when(resultMerger.merge(anyList(), eq(0))).thenReturn(mergedResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(mergedResult, context.getSearchResult());
|
||||
assertEquals(3, context.getSampleListIds().size());
|
||||
verify(taskFaceService, times(2)).searchFace(any(), anyString(), anyString(), anyString());
|
||||
verify(resultMerger, times(1)).merge(anyList(), eq(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Mode1_Intersection() throws Exception {
|
||||
// Given: 模式1(交集)
|
||||
context.setFaceSelectPostMode(1);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
FaceSampleEntity sample2 = createSample(102L, "http://example.com/s2.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1, sample2));
|
||||
context.setFaceSampleIds(Arrays.asList(101L, 102L));
|
||||
|
||||
SearchFaceRespVo searchResult1 = new SearchFaceRespVo();
|
||||
searchResult1.setSampleListIds(Arrays.asList(201L, 202L, 203L));
|
||||
|
||||
SearchFaceRespVo searchResult2 = new SearchFaceRespVo();
|
||||
searchResult2.setSampleListIds(Arrays.asList(202L, 203L, 204L));
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult1, searchResult2);
|
||||
|
||||
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
||||
mergedResult.setSampleListIds(Arrays.asList(202L, 203L)); // 交集
|
||||
|
||||
when(resultMerger.merge(anyList(), eq(1))).thenReturn(mergedResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(2, context.getSampleListIds().size());
|
||||
verify(resultMerger, times(1)).merge(anyList(), eq(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_DefaultMode() throws Exception {
|
||||
// Given: 未设置faceSelectPostMode,默认为0
|
||||
context.setFaceSelectPostMode(null);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1));
|
||||
context.setFaceSampleIds(Arrays.asList(101L));
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setSampleListIds(Arrays.asList(201L));
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
||||
mergedResult.setSampleListIds(Arrays.asList(201L));
|
||||
|
||||
when(resultMerger.merge(anyList(), eq(0))).thenReturn(mergedResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(resultMerger, times(1)).merge(anyList(), eq(0)); // 默认模式0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_PartialSearchFailure_Success() throws Exception {
|
||||
// Given: 部分样本搜索失败,但至少有一个成功
|
||||
context.setFaceSelectPostMode(0);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
FaceSampleEntity sample2 = createSample(102L, "http://example.com/s2.jpg");
|
||||
FaceSampleEntity sample3 = createSample(103L, "http://example.com/s3.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1, sample2, sample3));
|
||||
|
||||
SearchFaceRespVo searchResult1 = new SearchFaceRespVo();
|
||||
searchResult1.setSampleListIds(Arrays.asList(201L));
|
||||
|
||||
when(taskFaceService.searchFace(eq(faceBodyAdapter), eq("10"), eq("http://example.com/s1.jpg"), anyString()))
|
||||
.thenReturn(searchResult1);
|
||||
when(taskFaceService.searchFace(eq(faceBodyAdapter), eq("10"), eq("http://example.com/s2.jpg"), anyString()))
|
||||
.thenThrow(new RuntimeException("Network error"));
|
||||
when(taskFaceService.searchFace(eq(faceBodyAdapter), eq("10"), eq("http://example.com/s3.jpg"), anyString()))
|
||||
.thenReturn(null);
|
||||
|
||||
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
||||
mergedResult.setSampleListIds(Arrays.asList(201L));
|
||||
|
||||
when(resultMerger.merge(anyList(), eq(0))).thenReturn(mergedResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
verify(taskFaceService, times(3)).searchFace(any(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AllSearchFailure_ThrowException() throws Exception {
|
||||
// Given: 所有样本搜索都失败
|
||||
context.setFaceSelectPostMode(0);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
FaceSampleEntity sample2 = createSample(102L, "http://example.com/s2.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1, sample2));
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenThrow(new RuntimeException("Network error"));
|
||||
|
||||
// When & Then
|
||||
assertThrows(BaseException.class, () -> {
|
||||
stage.execute(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_BaseException_Rethrow() throws Exception {
|
||||
// Given
|
||||
context.setFaceSelectPostMode(0);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1));
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
BaseException baseException = new BaseException("服务不可用");
|
||||
when(resultMerger.merge(anyList(), anyInt())).thenThrow(baseException);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BaseException.class, () -> {
|
||||
stage.execute(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_GenericException_Failed() throws Exception {
|
||||
// Given
|
||||
context.setFaceSelectPostMode(0);
|
||||
|
||||
FaceSampleEntity sample1 = createSample(101L, "http://example.com/s1.jpg");
|
||||
context.setFaceSamples(Arrays.asList(sample1));
|
||||
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
when(resultMerger.merge(anyList(), anyInt())).thenThrow(new RuntimeException("Merge error"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
assertTrue(result.getMessage().contains("自定义人脸搜索失败"));
|
||||
}
|
||||
|
||||
private FaceSampleEntity createSample(Long id, String faceUrl) {
|
||||
FaceSampleEntity sample = new FaceSampleEntity();
|
||||
sample.setId(id);
|
||||
sample.setFaceUrl(faceUrl);
|
||||
return sample;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package com.ycwl.basic.face.pipeline.stages;
|
||||
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
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.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
import com.ycwl.basic.service.task.TaskFaceService;
|
||||
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.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* FaceRecognitionStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class FaceRecognitionStageTest {
|
||||
|
||||
@Mock
|
||||
private TaskFaceService taskFaceService;
|
||||
|
||||
@Mock
|
||||
private IFaceBodyAdapter faceBodyAdapter;
|
||||
|
||||
@InjectMocks
|
||||
private FaceRecognitionStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
private FaceEntity face;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
face = new FaceEntity();
|
||||
face.setId(1L);
|
||||
face.setScenicId(10L);
|
||||
face.setFaceUrl("http://example.com/face.jpg");
|
||||
context.setFace(face);
|
||||
context.setFaceBodyAdapter(faceBodyAdapter);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success() throws Exception {
|
||||
// Given
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.85f);
|
||||
searchResult.setSampleListIds(Arrays.asList(101L, 102L, 103L));
|
||||
|
||||
when(taskFaceService.searchFace(
|
||||
eq(faceBodyAdapter),
|
||||
eq("10"),
|
||||
eq("http://example.com/face.jpg"),
|
||||
eq("人脸识别")
|
||||
)).thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(searchResult, context.getSearchResult());
|
||||
assertEquals(0.85f, context.getSearchResult().getScore(), 0.0001);
|
||||
assertEquals(3, context.getSearchResult().getSampleListIds().size());
|
||||
verify(taskFaceService, times(1)).searchFace(any(), anyString(), anyString(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SearchResultNull_Failed() throws Exception {
|
||||
// Given
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
assertTrue(result.getMessage().contains("人脸识别失败"));
|
||||
assertNull(context.getSearchResult());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_SearchResultEmptyList() throws Exception {
|
||||
// Given
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.45f); // 低分
|
||||
searchResult.setSampleListIds(Arrays.asList()); // 空列表
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess()); // 即使没有匹配也成功
|
||||
assertEquals(searchResult, context.getSearchResult());
|
||||
assertEquals(0, context.getSearchResult().getSampleListIds().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_BaseException_Throw() throws Exception {
|
||||
// Given
|
||||
BaseException baseException = new BaseException("识别服务异常");
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenThrow(baseException);
|
||||
|
||||
// When & Then
|
||||
assertThrows(BaseException.class, () -> {
|
||||
stage.execute(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_GenericException_Failed() throws Exception {
|
||||
// Given
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenThrow(new RuntimeException("Network error"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
assertTrue(result.getMessage().contains("人脸识别失败"));
|
||||
assertNotNull(result.getException());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_NullFaceUrl() throws Exception {
|
||||
// Given
|
||||
face.setFaceUrl(null);
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), isNull(), anyString()))
|
||||
.thenThrow(new IllegalArgumentException("Face URL is null"));
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_HighScoreResult() throws Exception {
|
||||
// Given: 高分匹配
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.95f);
|
||||
searchResult.setSampleListIds(Arrays.asList(101L));
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(0.95f, context.getSearchResult().getScore(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_MultipleMatches() throws Exception {
|
||||
// Given: 多个匹配结果
|
||||
SearchFaceRespVo searchResult = new SearchFaceRespVo();
|
||||
searchResult.setScore(0.78f);
|
||||
searchResult.setSampleListIds(Arrays.asList(101L, 102L, 103L, 104L, 105L));
|
||||
|
||||
when(taskFaceService.searchFace(any(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(searchResult);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(5, context.getSearchResult().getSampleListIds().size());
|
||||
assertTrue(result.getMessage().contains("匹配数=5"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
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.facebody.adapter.IFaceBodyAdapter;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.service.pc.ScenicService;
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* PrepareContextStage 单元测试
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PrepareContextStageTest {
|
||||
|
||||
@Mock
|
||||
private FaceRepository faceRepository;
|
||||
|
||||
@Mock
|
||||
private ScenicRepository scenicRepository;
|
||||
|
||||
@Mock
|
||||
private ScenicService scenicService;
|
||||
|
||||
@Mock
|
||||
private ScenicConfigManager scenicConfig;
|
||||
|
||||
@Mock
|
||||
private IFaceBodyAdapter faceBodyAdapter;
|
||||
|
||||
@InjectMocks
|
||||
private PrepareContextStage stage;
|
||||
|
||||
private FaceMatchingContext context;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_Success() {
|
||||
// Given
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 0); // isManual=0
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
when(scenicRepository.getScenicConfigManager(10L)).thenReturn(scenicConfig);
|
||||
when(scenicService.getScenicFaceBodyAdapter(10L)).thenReturn(faceBodyAdapter);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(face, context.getFace());
|
||||
assertEquals(scenicConfig, context.getScenicConfig());
|
||||
assertEquals(faceBodyAdapter, context.getFaceBodyAdapter());
|
||||
verify(faceRepository, times(1)).getFace(1L);
|
||||
verify(scenicRepository, times(1)).getScenicConfigManager(10L);
|
||||
verify(scenicService, times(1)).getScenicFaceBodyAdapter(10L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_FaceNotFound_Failed() {
|
||||
// Given
|
||||
when(faceRepository.getFace(1L)).thenReturn(null);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
assertTrue(result.getMessage().contains("人脸不存在"));
|
||||
assertNull(context.getFace());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ManualFaceAndNotNew_Skipped() {
|
||||
// Given: isManual=1, isNew=false
|
||||
context = FaceMatchingContext.forAutoMatching(1L, false);
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 1); // isManual=1
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSkipped());
|
||||
assertTrue(result.getMessage().contains("人工选择的人脸"));
|
||||
assertEquals(face, context.getFace());
|
||||
// 人工选择的人脸跳过,不加载景区配置
|
||||
verify(scenicRepository, never()).getScenicConfigManager(anyLong());
|
||||
verify(scenicService, never()).getScenicFaceBodyAdapter(anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_ManualFaceButIsNew_Success() {
|
||||
// Given: isManual=1, isNew=true (新用户即使人工选择也需要匹配)
|
||||
context = FaceMatchingContext.forAutoMatching(1L, true);
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 1); // isManual=1
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
when(scenicRepository.getScenicConfigManager(10L)).thenReturn(scenicConfig);
|
||||
when(scenicService.getScenicFaceBodyAdapter(10L)).thenReturn(faceBodyAdapter);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(face, context.getFace());
|
||||
assertEquals(scenicConfig, context.getScenicConfig());
|
||||
assertEquals(faceBodyAdapter, context.getFaceBodyAdapter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_FaceBodyAdapterNotAvailable_Failed() {
|
||||
// Given
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 0);
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
when(scenicRepository.getScenicConfigManager(10L)).thenReturn(scenicConfig);
|
||||
when(scenicService.getScenicFaceBodyAdapter(10L)).thenReturn(null); // 无适配器
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isFailed());
|
||||
assertTrue(result.getMessage().contains("人脸识别服务不可用"));
|
||||
assertEquals(face, context.getFace());
|
||||
assertEquals(scenicConfig, context.getScenicConfig());
|
||||
assertNull(context.getFaceBodyAdapter());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_AutoMatchingOldUser() {
|
||||
// Given
|
||||
context = FaceMatchingContext.forAutoMatching(1L, false); // 老用户
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 0);
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
when(scenicRepository.getScenicConfigManager(10L)).thenReturn(scenicConfig);
|
||||
when(scenicService.getScenicFaceBodyAdapter(10L)).thenReturn(faceBodyAdapter);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertFalse(context.isNew());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExecute_RecognitionOnlyScene() {
|
||||
// Given
|
||||
context = FaceMatchingContext.forRecognitionOnly(1L);
|
||||
FaceEntity face = createFace(1L, 100L, 10L, 0);
|
||||
when(faceRepository.getFace(1L)).thenReturn(face);
|
||||
when(scenicRepository.getScenicConfigManager(10L)).thenReturn(scenicConfig);
|
||||
when(scenicService.getScenicFaceBodyAdapter(10L)).thenReturn(faceBodyAdapter);
|
||||
|
||||
// When
|
||||
StageResult<FaceMatchingContext> result = stage.execute(context);
|
||||
|
||||
// Then
|
||||
assertTrue(result.isSuccess());
|
||||
assertEquals(FaceMatchingScene.RECOGNITION_ONLY, context.getScene());
|
||||
}
|
||||
|
||||
private FaceEntity createFace(Long id, Long memberId, Long scenicId, Integer isManual) {
|
||||
FaceEntity face = new FaceEntity();
|
||||
face.setId(id);
|
||||
face.setMemberId(memberId);
|
||||
face.setScenicId(scenicId);
|
||||
face.setIsManual(isManual);
|
||||
face.setFaceUrl("http://example.com/face.jpg");
|
||||
return face;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user