task-143: publish 文件级 file 状态语义契约(ready 由 result.success+url 推导、job 字段独立、多文件混合、无结果 null)+ 8 条测试

This commit is contained in:
2026-09-02 05:46:42 +08:00
parent 9e58d28a99
commit 5e5056b99d
@@ -0,0 +1,170 @@
package com.nanri.aiimage.modules.task.contract;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.config.InstanceMetadata;
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
import com.nanri.aiimage.modules.publish.mapper.PublishFileMapper;
import com.nanri.aiimage.modules.publish.mapper.PublishItemMapper;
import com.nanri.aiimage.modules.publish.model.vo.PublishResultVo;
import com.nanri.aiimage.modules.publish.service.PublishTaskService;
import com.nanri.aiimage.modules.publish.service.PublishWorkbookService;
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
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.TaskFileJobEntity;
import com.nanri.aiimage.modules.task.service.TaskDistributedLockService;
import com.nanri.aiimage.modules.task.service.TaskFileJobService;
import com.nanri.aiimage.modules.task.service.TaskProgressLightAssembler;
import com.nanri.aiimage.modules.task.service.TransientPayloadStorageService;
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.support.TransactionTemplate;
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-143publish 文件级 file 状态语义契约(plan 08)。
* publish 的 file 状态为文件级(PublishResultVo.fileReady 由 result.success+url
* 推导,PublishItemsPageVo.fileStatus = file.status),与其余模块 job 级不同;
* 语义固化。
*/
@ExtendWith(MockitoExtension.class)
class PublishFileLevelStatusTest {
private static final Long RESULT_ID = 9201L;
@Mock private LocalFileStorageService localFileStorageService;
@Mock private ZiniaoShopSwitchService ziniaoShopSwitchService;
@Mock private PublishWorkbookService workbookService;
@Mock private PublishFileMapper publishFileMapper;
@Mock private PublishItemMapper publishItemMapper;
@Mock private FileTaskMapper fileTaskMapper;
@Mock private FileResultMapper fileResultMapper;
@Mock private TaskChunkMapper taskChunkMapper;
@Mock private TaskScopeStateMapper taskScopeStateMapper;
@Mock private TaskFileJobService taskFileJobService;
@Mock private TaskDistributedLockService taskDistributedLockService;
@Mock private TransientPayloadStorageService transientPayloadStorageService;
@Mock private OssStorageService ossStorageService;
@Mock private ObjectMapper objectMapper;
@Mock private TransactionTemplate transactionTemplate;
@Mock private InstanceMetadata instanceMetadata;
@Mock private TaskProgressLightAssembler taskProgressLightAssembler;
private PublishTaskService service;
@BeforeEach
void setUp() {
service = new PublishTaskService(
localFileStorageService, ziniaoShopSwitchService, workbookService, publishFileMapper,
publishItemMapper, fileTaskMapper, fileResultMapper, taskChunkMapper, taskScopeStateMapper,
taskFileJobService, taskDistributedLockService, transientPayloadStorageService, ossStorageService,
objectMapper, transactionTemplate, instanceMetadata, taskProgressLightAssembler);
lenient().when(ossStorageService.generateFreshDownloadUrl(any()))
.thenReturn("https://dl.example/publish-result.zip");
}
private PublishResultVo toVo(FileResultEntity result, TaskFileJobEntity job) {
return (PublishResultVo) ReflectionTestUtils.invokeMethod(service, "toResultVo", result, job);
}
private FileResultEntity result(int success, String url) {
FileResultEntity result = new FileResultEntity();
result.setId(RESULT_ID);
result.setSuccess(success);
result.setResultFileUrl(url);
result.setResultFilename("PUBLISH-1_上架结果.zip");
return result;
}
private TaskFileJobEntity job() {
TaskFileJobEntity job = new TaskFileJobEntity();
job.setId(9301L);
job.setStatus("RUNNING");
job.setRetryCount(1);
job.setErrorMessage("写入慢");
return job;
}
@Test
void readyTrueWhenSuccessAndUrlPresent() {
PublishResultVo vo = toVo(result(1, "oss://result/p.zip"), null);
assertTrue(vo.getFileReady());
assertEquals("https://dl.example/publish-result.zip", vo.getDownloadUrl());
}
@Test
void readyFalseWhenUrlMissing() {
PublishResultVo vo = toVo(result(1, null), null);
assertFalse(vo.getFileReady());
assertNull(vo.getDownloadUrl(), "未 ready 不生成下载直链");
}
@Test
void failedFileNeverReady() {
PublishResultVo vo = toVo(result(0, "oss://result/p.zip"), null);
assertFalse(vo.getFileReady(), "success=0 的文件不算 ready");
assertNull(vo.getDownloadUrl());
}
@Test
void perFileReadyIsIndependent() {
PublishResultVo ready = toVo(result(1, "oss://a.zip"), null);
PublishResultVo pending = toVo(result(1, null), null);
assertTrue(ready.getFileReady());
assertFalse(pending.getFileReady());
}
@Test
void mixedFilesShowMixedReadyState() {
PublishResultVo failed = toVo(result(0, "oss://f.zip"), null);
PublishResultVo ready = toVo(result(1, "oss://r.zip"), null);
assertFalse(failed.getFileReady());
assertTrue(ready.getFileReady());
}
@Test
void noResultReturnsNull() {
assertNull(toVo(null, null), "无结果记录不返回 VO");
}
@Test
void jobFieldsMappedWhenJobPresent() {
PublishResultVo vo = toVo(result(1, "oss://p.zip"), job());
assertEquals(9301L, vo.getFileJobId());
assertEquals("RUNNING", vo.getFileJobStatus());
assertEquals(1, vo.getFileJobRetryCount());
assertEquals("写入慢", vo.getFileJobError());
}
@Test
void semanticsFrozen() {
PublishResultVo vo = toVo(result(1, "oss://p.zip"), job());
// 快照:ready 由 result 行推导(非 job),job 字段独立携带(文件级语义)
assertTrue(vo.getFileReady());
assertEquals("PUBLISH-1_上架结果.zip", vo.getResultFilename());
assertEquals(RESULT_ID, vo.getResultId());
assertEquals("RUNNING", vo.getFileJobStatus());
}
}