task-141: similarasin file 进度合成单测(LLM/Python 上传/snapshot 三路径、fileReady 恒 100%、除零与状态边界)+ 8 条测试

This commit is contained in:
2026-09-02 05:38:26 +08:00
parent b7a403dc22
commit 89d5eda057
@@ -0,0 +1,212 @@
package com.nanri.aiimage.modules.similarasin.service.support;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
import com.nanri.aiimage.modules.similarasin.model.vo.SimilarAsinHistoryItemVo;
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
import com.nanri.aiimage.modules.task.mapper.TaskChunkMapper;
import com.nanri.aiimage.modules.task.mapper.TaskScopeStateMapper;
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskFileJobEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskProgressSnapshotEntity;
import com.nanri.aiimage.modules.task.model.entity.TaskScopeStateEntity;
import com.nanri.aiimage.modules.task.service.TaskProgressSnapshotService;
import com.nanri.aiimage.modules.task.service.TransientPayloadStorageService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
/**
* task-141similarasin file 进度合成逻辑单测(plan 08)。
* attachFileProgress 三路径:LLM 阶段(completed/pending>0)、Python 上传阶段、
* snapshot 百分比(上传完成无 LLM);fileReady=true 时恒 100%;边界安全。
*/
@ExtendWith(MockitoExtension.class)
class FileProgressSynthesisTest {
private static final Long TASK_ID = 2222L;
private static final Long RESULT_ID = 2223L;
@Mock private TaskScopeStateMapper taskScopeStateMapper;
@Mock private TaskChunkMapper taskChunkMapper;
@Mock private FileTaskMapper fileTaskMapper;
@Mock private TaskProgressSnapshotService taskProgressSnapshotService;
@Mock private OssStorageService ossStorageService;
@Mock private TransientPayloadStorageService transientPayloadStorageService;
@Mock private ObjectMapper objectMapper;
private SimilarAsinHistoryAssembler assembler;
@BeforeAll
static void initializeMybatisMetadata() {
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
TableInfoHelper.initTableInfo(assistant, FileResultEntity.class);
TableInfoHelper.initTableInfo(assistant, TaskScopeStateEntity.class);
}
@BeforeEach
void setUp() {
assembler = new SimilarAsinHistoryAssembler(taskScopeStateMapper, taskChunkMapper, fileTaskMapper,
taskProgressSnapshotService, ossStorageService, transientPayloadStorageService, objectMapper);
lenient().when(taskScopeStateMapper.selectCount(any())).thenReturn(0L);
lenient().when(taskScopeStateMapper.selectList(any())).thenReturn(List.of());
lenient().when(taskProgressSnapshotService.find(any(), any())).thenReturn(null);
lenient().when(taskChunkMapper.selectCount(any())).thenReturn(0L);
lenient().when(ossStorageService.generateFreshDownloadUrl(any())).thenReturn("https://dl.example/x.xlsx");
}
private SimilarAsinHistoryItemVo build(Runnable configure, FileResultEntity row, FileTaskEntity task,
TaskFileJobEntity job) {
configure.run();
return assembler.buildHistoryItems(List.of(row),
task == null ? Map.of() : Map.of(TASK_ID, task),
job == null ? Map.of() : Map.of(RESULT_ID, job)).getFirst();
}
private FileResultEntity resultRow() {
FileResultEntity row = new FileResultEntity();
row.setId(RESULT_ID);
row.setTaskId(TASK_ID);
row.setSourceFilename("source.xlsx");
row.setResultFileUrl(null);
row.setSuccess(0);
return row;
}
private FileTaskEntity runningTask() {
FileTaskEntity task = new FileTaskEntity();
task.setId(TASK_ID);
task.setModuleType("SIMILAR_ASIN");
task.setStatus("RUNNING");
return task;
}
@Test
void fileReadyPathIsAlwaysOneHundredPercent() {
FileResultEntity row = resultRow();
row.setResultFileUrl("oss://result/source-result.xlsx");
SimilarAsinHistoryItemVo vo = build(() -> {
}, row, runningTask(), null);
assertEquals(100, vo.getFileProgressPercent());
assertEquals(1, vo.getFileProgressCurrent());
assertEquals(1, vo.getFileProgressTotal());
assertEquals("结果文件已生成", vo.getFileProgressMessage());
assertEquals(Boolean.TRUE, vo.getFileReady());
}
@Test
void llmProgressPathWhenCompletedExists() {
when(taskScopeStateMapper.selectCount(any())).thenReturn(2L, 0L);
lenient().when(taskChunkMapper.selectCount(any())).thenReturn(1L);
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNotNull(vo.getFileProgressPercent(), "LLM 阶段必须合成进度");
assertEquals(2, vo.getFileProgressCurrent());
}
@Test
void pythonUploadPathWhenNoLlmStates() {
lenient().when(taskChunkMapper.selectCount(any())).thenReturn(1L);
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNotNull(vo.getFileProgressPercent(), "Python 上传阶段必须合成进度");
assertNotNull(vo.getFileProgressMessage());
}
@Test
void snapshotPercentPathWhenUploadComplete() {
TaskScopeStateEntity scope = new TaskScopeStateEntity();
scope.setTaskId(TASK_ID);
scope.setCompleted(1);
when(taskScopeStateMapper.selectList(any())).thenReturn(List.of(scope));
TaskProgressSnapshotEntity snapshot = new TaskProgressSnapshotEntity();
snapshot.setTotalCount(10);
snapshot.setSuccessCount(5);
snapshot.setMessage("组装中");
when(taskProgressSnapshotService.find(eq(TASK_ID), eq("SIMILAR_ASIN"))).thenReturn(snapshot);
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNotNull(vo.getFileProgressPercent(), "上传完成无 LLM 时走 snapshot 百分比");
assertEquals(5, vo.getFileProgressCurrent());
assertEquals(10, vo.getFileProgressTotal());
}
@Test
void pendingOnlyStillUsesLlmPath() {
when(taskScopeStateMapper.selectCount(any())).thenReturn(0L, 2L);
lenient().when(taskChunkMapper.selectCount(any())).thenReturn(1L);
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNotNull(vo.getFileProgressPercent(), "pending>0 也走 LLM 路径");
}
@Test
void uploadCompleteWithoutLlmOrSnapshotSetsNoProgress() {
TaskScopeStateEntity scope = new TaskScopeStateEntity();
scope.setTaskId(TASK_ID);
scope.setCompleted(1);
when(taskScopeStateMapper.selectList(any())).thenReturn(List.of(scope));
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNull(vo.getFileProgressPercent(), "无 LLM 无 snapshot 不合成进度");
}
@Test
void nonRunningTaskWithoutProgressSkipsSynthesis() {
FileTaskEntity failed = runningTask();
failed.setStatus("FAILED");
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), failed, null);
assertNull(vo.getFileProgressPercent(), "非 RUNNING 任务不合成上传进度");
}
@Test
void zeroTotalSnapshotIsDivisionSafe() {
TaskScopeStateEntity scope = new TaskScopeStateEntity();
scope.setTaskId(TASK_ID);
scope.setCompleted(1);
when(taskScopeStateMapper.selectList(any())).thenReturn(List.of(scope));
TaskProgressSnapshotEntity snapshot = new TaskProgressSnapshotEntity();
snapshot.setTotalCount(0);
snapshot.setSuccessCount(0);
when(taskProgressSnapshotService.find(eq(TASK_ID), eq("SIMILAR_ASIN"))).thenReturn(snapshot);
SimilarAsinHistoryItemVo vo = build(() -> {
}, resultRow(), runningTask(), null);
assertNull(vo.getFileProgressPercent(), "total=0 时不除零不合成");
}
}