task-142: fileReady/fileStatus 语义契约(URL 判定、无 job SUCCESS/null、job 状态与错误映射、空白边界)+ 8 条测试

This commit is contained in:
2026-09-02 05:42:32 +08:00
parent 89d5eda057
commit 9e58d28a99
@@ -0,0 +1,168 @@
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.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.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
/**
* task-142fileReady/fileStatus 语义契约(plan 08)。
* fileReady = resultFileUrl 非空;无 job 时 fileStatus = ready?SUCCESS:null
* 有 job 时 fileStatus = job.statusfileError = job.errorMessage。
*/
@ExtendWith(MockitoExtension.class)
class FilePhaseSemanticsTest {
private static final Long TASK_ID = 1212L;
private static final Long RESULT_ID = 1213L;
@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(FileResultEntity row, TaskFileJobEntity job) {
FileTaskEntity task = new FileTaskEntity();
task.setId(TASK_ID);
task.setModuleType("SIMILAR_ASIN");
task.setStatus("RUNNING");
return assembler.buildHistoryItems(List.of(row), Map.of(TASK_ID, task),
job == null ? Map.of() : Map.of(RESULT_ID, job)).getFirst();
}
private FileResultEntity row(String url) {
FileResultEntity row = new FileResultEntity();
row.setId(RESULT_ID);
row.setTaskId(TASK_ID);
row.setSourceFilename("source.xlsx");
row.setResultFileUrl(url);
row.setSuccess(0);
return row;
}
private TaskFileJobEntity job(String status, String error) {
TaskFileJobEntity job = new TaskFileJobEntity();
job.setId(901L);
job.setTaskId(TASK_ID);
job.setResultId(RESULT_ID);
job.setStatus(status);
job.setErrorMessage(error);
return job;
}
@Test
void readyTrueWhenUrlPresent() {
SimilarAsinHistoryItemVo vo = build(row("oss://result/a.xlsx"), null);
assertEquals(Boolean.TRUE, vo.getFileReady());
}
@Test
void readyFalseWhenUrlBlank() {
SimilarAsinHistoryItemVo vo = build(row(null), null);
assertFalse(Boolean.TRUE.equals(vo.getFileReady()));
}
@Test
void noJobWithReadyMapsToSuccessStatus() {
SimilarAsinHistoryItemVo vo = build(row("oss://result/a.xlsx"), null);
assertEquals("SUCCESS", vo.getFileStatus(), "无 job 且 ready 时 fileStatus=SUCCESS");
}
@Test
void noJobWithoutReadyMapsToNullStatus() {
SimilarAsinHistoryItemVo vo = build(row(null), null);
assertNull(vo.getFileStatus(), "无 job 且未 ready 时 fileStatus=null");
assertNull(vo.getFileJobId());
}
@Test
void jobStatusMappedDirectly() {
SimilarAsinHistoryItemVo vo = build(row("oss://result/a.xlsx"), job("RUNNING", null));
assertEquals("RUNNING", vo.getFileStatus(), "有 job 时 fileStatus=job.status");
assertEquals(901L, vo.getFileJobId());
}
@Test
void jobErrorMappedDirectly() {
SimilarAsinHistoryItemVo vo = build(row(null), job("FAILED", "组装失败: 超时"));
assertEquals("组装失败: 超时", vo.getFileError(), "fileError=job.errorMessage");
assertEquals("FAILED", vo.getFileStatus());
}
@Test
void blankWhitespaceUrlIsNotReady() {
SimilarAsinHistoryItemVo vo = build(row(" "), null);
assertFalse(Boolean.TRUE.equals(vo.getFileReady()), "空白字符串不算 ready");
assertNull(vo.getFileStatus());
}
@Test
void semanticsFrozenCombined() {
// 组合快照:URL + SUCCESS job → ready=true、status=SUCCESS、error 映射、jobId 携带
SimilarAsinHistoryItemVo vo = build(row("oss://result/a.xlsx"), job("SUCCESS", null));
assertEquals(Boolean.TRUE, vo.getFileReady());
assertEquals("SUCCESS", vo.getFileStatus());
assertNull(vo.getFileError());
assertEquals(901L, vo.getFileJobId());
assertTrue(vo.getFileReady());
}
}