完成上架相关优化、oss迁移

This commit is contained in:
supernijia
2026-07-25 17:57:34 +08:00
parent 8ab36b1aaf
commit 4ddb8b47b0
28 changed files with 964 additions and 382 deletions
@@ -2,12 +2,16 @@ package com.nanri.aiimage.modules.imagevideo.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.config.ImageVideoProperties;
import com.nanri.aiimage.config.InstanceMetadata;
import com.nanri.aiimage.common.exception.TaskOwnerMismatchException;
import com.nanri.aiimage.modules.file.service.oss.OssStorageService;
import com.nanri.aiimage.modules.imagevideo.mapper.ImageVideoAsyncTaskMapper;
import com.nanri.aiimage.modules.imagevideo.model.dto.DouyinCopyRequest;
import com.nanri.aiimage.modules.imagevideo.model.entity.ImageVideoAsyncTaskEntity;
import com.nanri.aiimage.modules.imagevideo.model.vo.DouyinCopyVo;
import org.junit.jupiter.api.Test;
import org.springframework.core.task.TaskExecutor;
import org.mockito.ArgumentCaptor;
import java.time.LocalDateTime;
import java.util.List;
@@ -15,6 +19,7 @@ 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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -55,7 +60,8 @@ class ImageVideoAsyncTaskServiceTest {
ObjectMapper objectMapper = new ObjectMapper();
TaskExecutor directExecutor = Runnable::run;
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper, cozeService, workflowConfigService, mock(ImageVideoArchiveService.class), objectMapper, directExecutor);
taskMapper, cozeService, workflowConfigService, mock(ImageVideoArchiveService.class), objectMapper,
directExecutor, instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingDouyinTask();
Map<String, Object> cozeResult = Map.of("data", List.of(Map.of(
@@ -67,7 +73,7 @@ class ImageVideoAsyncTaskServiceTest {
copyResult.setScriptDraft("这条裙裤绝了,遮肉显腿长,快拍!");
when(taskMapper.selectList(any())).thenReturn(List.of(task));
when(taskMapper.claimWaiting(79L)).thenReturn(1);
when(taskMapper.claimWaiting(79L, "server-121")).thenReturn(1);
when(taskMapper.selectById(79L)).thenReturn(task);
when(workflowConfigService.douyinCopyWorkflowId()).thenReturn("7652941112982798388");
when(cozeService.getWorkflowResult(1L, "7652941112982798388", "7662364426301964329"))
@@ -90,13 +96,14 @@ class ImageVideoAsyncTaskServiceTest {
ImageVideoCozeService cozeService = mock(ImageVideoCozeService.class);
ImageVideoWorkflowConfigService workflowConfigService = mock(ImageVideoWorkflowConfigService.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper, cozeService, workflowConfigService, mock(ImageVideoArchiveService.class), new ObjectMapper(), Runnable::run);
taskMapper, cozeService, workflowConfigService, mock(ImageVideoArchiveService.class), new ObjectMapper(),
Runnable::run, instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingDouyinTask();
Map<String, Object> cozeResult = Map.of("data", List.of(Map.of("execute_status", "Running")));
when(taskMapper.selectList(any())).thenReturn(List.of(task));
when(taskMapper.claimWaiting(79L)).thenReturn(1);
when(taskMapper.claimWaiting(79L, "server-121")).thenReturn(1);
when(taskMapper.selectById(79L)).thenReturn(task);
when(workflowConfigService.douyinCopyWorkflowId()).thenReturn("7652941112982798388");
when(cozeService.getWorkflowResult(1L, "7652941112982798388", "7662364426301964329"))
@@ -117,7 +124,8 @@ class ImageVideoAsyncTaskServiceTest {
ImageVideoWorkflowConfigService workflowConfigService = mock(ImageVideoWorkflowConfigService.class);
ImageVideoArchiveService archiveService = mock(ImageVideoArchiveService.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper, cozeService, workflowConfigService, archiveService, new ObjectMapper(), Runnable::run);
taskMapper, cozeService, workflowConfigService, archiveService, new ObjectMapper(), Runnable::run,
instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingWorkflowTask();
Map<String, Object> cozeResult = Map.of("data", List.of(Map.of(
"execute_status", "Fail",
@@ -125,7 +133,7 @@ class ImageVideoAsyncTaskServiceTest {
"error_message", "model unavailable")));
when(taskMapper.selectList(any())).thenReturn(List.of(task));
when(taskMapper.claimWaiting(98L)).thenReturn(1);
when(taskMapper.claimWaiting(98L, "server-121")).thenReturn(1);
when(taskMapper.selectById(98L)).thenReturn(task);
when(workflowConfigService.imageVideoWorkflowId()).thenReturn("workflow-1");
when(cozeService.getWorkflowResult(1L, "workflow-1", "exec-98")).thenReturn(cozeResult);
@@ -148,7 +156,8 @@ class ImageVideoAsyncTaskServiceTest {
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run);
Runnable::run,
instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingWorkflowTask();
task.setStatus("FAILED");
task.setCozeStatus("FAIL");
@@ -171,7 +180,8 @@ class ImageVideoAsyncTaskServiceTest {
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run);
Runnable::run,
instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingWorkflowTask();
task.setStatus("FAILED");
task.setCozeStatus("RUNNING");
@@ -194,7 +204,8 @@ class ImageVideoAsyncTaskServiceTest {
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run);
Runnable::run,
instanceMetadata("server-121"));
when(taskMapper.delete(any())).thenReturn(3);
service.cleanupFailedTasks();
@@ -202,10 +213,107 @@ class ImageVideoAsyncTaskServiceTest {
verify(taskMapper).delete(any());
}
@Test
void submittedTaskIsBoundToCurrentInstance() {
ImageVideoAsyncTaskMapper taskMapper = mock(ImageVideoAsyncTaskMapper.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper,
mock(ImageVideoCozeService.class),
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run,
instanceMetadata("server-121"));
DouyinCopyRequest request = new DouyinCopyRequest();
request.setUserId(1L);
request.setUrl("https://example.com/video");
service.submitDouyinCopy(request);
ArgumentCaptor<ImageVideoAsyncTaskEntity> taskCaptor = ArgumentCaptor.forClass(ImageVideoAsyncTaskEntity.class);
verify(taskMapper).insert(taskCaptor.capture());
assertEquals("server-121", taskCaptor.getValue().getOwnerInstanceId());
}
@Test
void taskQueryOnAnotherInstanceTriggersOwnerForwarding() {
ImageVideoAsyncTaskMapper taskMapper = mock(ImageVideoAsyncTaskMapper.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper,
mock(ImageVideoCozeService.class),
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run,
instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingWorkflowTask();
task.setOwnerInstanceId("server-110");
when(taskMapper.selectOne(any())).thenReturn(task);
TaskOwnerMismatchException exception = assertThrows(
TaskOwnerMismatchException.class,
() -> service.getTask(98L, 1L));
assertEquals(98L, exception.getTaskId());
assertEquals("server-110", exception.getOwnerInstanceId());
assertEquals("server-121", exception.getCurrentInstanceId());
}
@Test
void applicationStartRequeuesInterruptedPollingTasksForOwner() {
ImageVideoAsyncTaskMapper taskMapper = mock(ImageVideoAsyncTaskMapper.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper,
mock(ImageVideoCozeService.class),
mock(ImageVideoWorkflowConfigService.class),
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run,
instanceMetadata("server-121"));
when(taskMapper.requeuePollingTasks("server-121")).thenReturn(2);
service.recoverInterruptedPollingTasks();
verify(taskMapper).requeuePollingTasks("server-121");
}
@Test
void overdueTaskStillConsumesTerminalCozeResultAfterRecovery() {
ImageVideoAsyncTaskMapper taskMapper = mock(ImageVideoAsyncTaskMapper.class);
ImageVideoCozeService cozeService = mock(ImageVideoCozeService.class);
ImageVideoWorkflowConfigService workflowConfigService = mock(ImageVideoWorkflowConfigService.class);
ImageVideoAsyncTaskService service = new ImageVideoAsyncTaskService(
taskMapper,
cozeService,
workflowConfigService,
mock(ImageVideoArchiveService.class),
new ObjectMapper(),
Runnable::run,
instanceMetadata("server-121"));
ImageVideoAsyncTaskEntity task = waitingWorkflowTask();
task.setSubmittedAt(LocalDateTime.now().minusHours(2));
Map<String, Object> cozeResult = Map.of("data", List.of(Map.of(
"execute_status", "Success",
"output", "{\"Output\":\"https://example.com/result.mp4\"}")));
when(taskMapper.selectList(any())).thenReturn(List.of(task));
when(taskMapper.claimWaiting(98L, "server-121")).thenReturn(1);
when(taskMapper.selectById(98L)).thenReturn(task);
when(workflowConfigService.imageVideoWorkflowId()).thenReturn("workflow-1");
when(cozeService.getWorkflowResult(1L, "workflow-1", "exec-98")).thenReturn(cozeResult);
service.pollWaitingTasks();
assertEquals("SUCCESS", task.getStatus());
assertEquals("SUCCESS", task.getCozeStatus());
assertNotNull(task.getCompletedAt());
}
private ImageVideoAsyncTaskEntity waitingDouyinTask() {
ImageVideoAsyncTaskEntity task = new ImageVideoAsyncTaskEntity();
task.setId(79L);
task.setUserId(1L);
task.setOwnerInstanceId("server-121");
task.setTaskType("DOUYIN_COPY");
task.setStatus("WAITING");
task.setCozeExecuteId("7662364426301964329");
@@ -222,4 +330,8 @@ class ImageVideoAsyncTaskServiceTest {
task.setCozeExecuteId("exec-98");
return task;
}
private InstanceMetadata instanceMetadata(String instanceId) {
return new InstanceMetadata(instanceId);
}
}
@@ -4,12 +4,16 @@ import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.common.exception.TaskOwnerMismatchException;
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.dto.PublishParseRequest;
import com.nanri.aiimage.modules.publish.model.dto.PublishResultFileDto;
import com.nanri.aiimage.modules.publish.model.dto.PublishRowDto;
import com.nanri.aiimage.modules.publish.model.dto.PublishSourceFileDto;
import com.nanri.aiimage.modules.publish.model.dto.PublishSubmitResultRequest;
import com.nanri.aiimage.modules.publish.model.entity.PublishFileEntity;
import com.nanri.aiimage.modules.publish.model.entity.PublishItemEntity;
@@ -20,6 +24,7 @@ import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
import com.nanri.aiimage.modules.task.service.TaskDistributedLockService;
import com.nanri.aiimage.modules.task.service.TaskFileJobService;
import com.nanri.aiimage.modules.ziniao.service.ZiniaoShopSwitchService;
import com.nanri.aiimage.modules.ziniao.model.vo.ZiniaoShopMatchResultVo;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@@ -29,9 +34,11 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.LocalDateTime;
import java.io.File;
import java.util.List;
import java.util.function.Consumer;
@@ -42,6 +49,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -74,11 +82,17 @@ class PublishTaskServiceTest {
@Mock private OssStorageService ossStorageService;
@Mock private ObjectMapper objectMapper;
@Mock private TransactionTemplate transactionTemplate;
@Mock private InstanceMetadata instanceMetadata;
@InjectMocks private PublishTaskService service;
@BeforeEach
void executeTransactionsInline() {
lenient().when(instanceMetadata.getInstanceId()).thenReturn("instance-a");
lenient().when(transactionTemplate.execute(any())).thenAnswer(invocation -> {
TransactionCallback<?> callback = invocation.getArgument(0);
return callback.doInTransaction(null);
});
lenient().doAnswer(invocation -> {
Consumer<TransactionStatus> callback = invocation.getArgument(0);
callback.accept(null);
@@ -86,6 +100,65 @@ class PublishTaskServiceTest {
}).when(transactionTemplate).executeWithoutResult(any());
}
@Test
void parseStoresCurrentInstanceAsTaskOwner() throws Exception {
PublishParseRequest request = new PublishParseRequest();
request.setUserId(7L);
request.setPublishCountry("DE");
PublishSourceFileDto source = new PublishSourceFileDto();
source.setFileKey("uploads/shop.xlsx");
source.setOriginalFilename("shop.xlsx");
request.setFiles(List.of(source));
File localFile = mock(File.class);
when(localFile.exists()).thenReturn(true);
when(localFileStorageService.findLocalSourceFile(source.getFileKey())).thenReturn(localFile);
when(ziniaoShopSwitchService.findIndexedStoreByName("shop", false)).thenReturn(
new ZiniaoShopMatchResultVo(true, "shop-1", "shop", "company", "Amazon",
7L, null, "MATCHED", null));
when(workbookService.parse(localFile)).thenReturn(
new PublishWorkbookService.ParsedWorkbook(List.of(row("1"))));
ObjectMapper realObjectMapper = new ObjectMapper();
when(objectMapper.writeValueAsString(any())).thenAnswer(invocation ->
realObjectMapper.writeValueAsString(invocation.getArgument(0)));
doAnswer(invocation -> {
FileTaskEntity inserted = invocation.getArgument(0);
inserted.setId(501L);
return 1;
}).when(fileTaskMapper).insert(any(FileTaskEntity.class));
doAnswer(invocation -> {
PublishFileEntity inserted = invocation.getArgument(0);
inserted.setId(601L);
return 1;
}).when(publishFileMapper).insert(any(PublishFileEntity.class));
service.parseAndCreateTask(request);
org.mockito.ArgumentCaptor<FileTaskEntity> taskCaptor =
org.mockito.ArgumentCaptor.forClass(FileTaskEntity.class);
verify(fileTaskMapper).insert(taskCaptor.capture());
assertEquals("instance-a", realObjectMapper.readTree(
taskCaptor.getValue().getResultJson()).path("ownerInstanceId").asText());
}
@Test
void taskAccessRejectsAnotherInstanceForExistingRouterToForward() throws Exception {
long taskId = 109L;
FileTaskEntity task = task(taskId, 7L, "RUNNING");
task.setResultJson("{\"ownerInstanceId\":\"instance-b\"}");
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
when(objectMapper.readTree(task.getResultJson())).thenReturn(
new ObjectMapper().readTree(task.getResultJson()));
TaskOwnerMismatchException error = assertThrows(TaskOwnerMismatchException.class,
() -> service.getTaskDetail(taskId, 7L));
assertEquals(taskId, error.getTaskId());
assertEquals("instance-b", error.getOwnerInstanceId());
assertEquals("instance-a", error.getCurrentInstanceId());
verifyNoInteractions(publishItemMapper);
}
@Test
void resultCallbackRejectsAnotherUserBeforeReadingOrReplacingRows() {
long taskId = 101L;
@@ -180,7 +253,8 @@ class PublishTaskServiceTest {
assertEquals("SUCCESS", file.getStatus());
verify(taskFileJobService).enqueueAssembleResult(
taskId, PublishTaskService.MODULE_TYPE, resultId, "task:" + taskId);
taskId, PublishTaskService.MODULE_TYPE, resultId,
"task:" + taskId + ":owner:instance-a");
verify(lock).close();
}
@@ -265,7 +339,8 @@ class PublishTaskServiceTest {
verify(taskFileJobService, times(2))
.countUnfinishedAssembleJobs(taskId, PublishTaskService.MODULE_TYPE);
verify(taskFileJobService).enqueueAssembleResult(
taskId, PublishTaskService.MODULE_TYPE, resultId, "task:" + taskId);
taskId, PublishTaskService.MODULE_TYPE, resultId,
"task:" + taskId + ":owner:instance-a");
verify(fileTaskMapper).updateById(task);
verify(fileResultMapper).updateById(result);
verify(lock).close();
@@ -22,6 +22,7 @@ import java.util.zip.ZipFile;
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.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -30,13 +31,22 @@ class PublishWorkbookServiceTest {
private final PublishWorkbookService service = new PublishWorkbookService();
@Test
void parsesOnlyTheExactEightHeadersAcrossNonEmptySheets() throws Exception {
void parsesOnlyFirstFiveColumnsAcrossNonEmptySheets() throws Exception {
Path directory = Files.createTempDirectory("publish-parse-");
try {
File valid = directory.resolve("valid.xlsx").toFile();
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream output = new FileOutputStream(valid)) {
writeSourceSheet(workbook.createSheet("英国数据"), "英国", "B001");
Sheet uk = workbook.createSheet("英国数据");
writeSourceSheet(uk, "英国", "B001");
for (int index = PublishWorkbookService.SOURCE_HEADERS.size();
index < PublishWorkbookService.RESULT_HEADERS.size(); index++) {
uk.getRow(0).createCell(index)
.setCellValue(PublishWorkbookService.RESULT_HEADERS.get(index));
}
uk.getRow(1).createCell(5).setCellValue("旧状态");
uk.getRow(1).createCell(6).setCellValue("旧同步状态");
uk.getRow(1).createCell(7).setCellValue("旧同步国家");
workbook.createSheet("空白页");
writeSourceSheet(workbook.createSheet("德国数据"), "DE", "B002");
workbook.write(output);
@@ -45,6 +55,9 @@ class PublishWorkbookServiceTest {
PublishWorkbookService.ParsedWorkbook parsed = service.parse(valid);
assertEquals(2, parsed.rows().size());
assertEquals("B001", parsed.rows().get(0).getAsin());
assertNull(parsed.rows().get(0).getStatus());
assertNull(parsed.rows().get(0).getSyncStatus());
assertNull(parsed.rows().get(0).getSyncCountries());
assertEquals("DE", parsed.rows().get(1).getCountry());
File invalid = directory.resolve("invalid.xlsx").toFile();
@@ -52,7 +65,7 @@ class PublishWorkbookServiceTest {
FileOutputStream output = new FileOutputStream(invalid)) {
Sheet sheet = workbook.createSheet("错误表头");
Row header = sheet.createRow(0);
List<String> headers = new ArrayList<>(PublishWorkbookService.HEADERS);
List<String> headers = new ArrayList<>(PublishWorkbookService.SOURCE_HEADERS);
headers.set(1, "Asin");
for (int index = 0; index < headers.size(); index++) {
header.createCell(index).setCellValue(headers.get(index));
@@ -82,8 +95,8 @@ class PublishWorkbookServiceTest {
Set.of(workbook.getSheetName(0), workbook.getSheetName(1)));
Sheet uk = workbook.getSheet("英国");
assertNotNull(uk);
for (int index = 0; index < PublishWorkbookService.HEADERS.size(); index++) {
assertEquals(PublishWorkbookService.HEADERS.get(index),
for (int index = 0; index < PublishWorkbookService.RESULT_HEADERS.size(); index++) {
assertEquals(PublishWorkbookService.RESULT_HEADERS.get(index),
uk.getRow(0).getCell(index).getStringCellValue());
}
assertEquals(CellType.NUMERIC, uk.getRow(1).getCell(4).getCellType());
@@ -124,8 +137,8 @@ class PublishWorkbookServiceTest {
private void writeSourceSheet(Sheet sheet, String country, String asin) {
Row header = sheet.createRow(0);
for (int index = 0; index < PublishWorkbookService.HEADERS.size(); index++) {
header.createCell(index).setCellValue(PublishWorkbookService.HEADERS.get(index));
for (int index = 0; index < PublishWorkbookService.SOURCE_HEADERS.size(); index++) {
header.createCell(index).setCellValue(PublishWorkbookService.SOURCE_HEADERS.get(index));
}
Row data = sheet.createRow(1);
data.createCell(0).setCellValue("1");
@@ -133,9 +146,6 @@ class PublishWorkbookServiceTest {
data.createCell(2).setCellValue(country);
data.createCell(3).setCellValue("Brand");
data.createCell(4).setCellValue(50);
data.createCell(5).setCellValue("成功");
data.createCell(6).setCellValue("成功");
data.createCell(7).setCellValue("德国,法国");
}
private PublishRowDto row(String id, String asin, String country, String price) {
@@ -3,10 +3,29 @@ package com.nanri.aiimage.modules.similarasin.service;
import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinResultRowDto;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class SimilarAsinTaskServiceTest {
@Test
@SuppressWarnings("unchecked")
void resultWorkbookRestoresPriceAfterCountryAndShiftsImageColumns() throws Exception {
Field headersField = SimilarAsinTaskService.class.getDeclaredField("RESULT_HEADERS");
headersField.setAccessible(true);
List<String> headers = (List<String>) headersField.get(null);
assertEquals(List.of(
"id", "asin", "国家", "价格", "卖家名称", "品牌", "是否有货", "相似度",
"是否符合类目", "不符合理由", "产品类目", "状态", "主图", "阿里巴巴图片1", "阿里巴巴图片2"
), headers);
assertEquals(12, staticIntField("IMG_COL_MAIN"));
assertEquals(13, staticIntField("IMG_COL_PUZZLE1"));
assertEquals(14, staticIntField("IMG_COL_PUZZLE2"));
}
@Test
void resultStatusUsesReturnedCozeDataAndImages() {
assertEquals("\u5931\u8d25", SimilarAsinTaskService.resolveResultStatus(null));
@@ -30,4 +49,10 @@ class SimilarAsinTaskServiceTest {
SimilarAsinResultRowDto withVisibleResultData = new SimilarAsinResultRowDto();
assertEquals("\u6210\u529f", SimilarAsinTaskService.resolveResultStatus(withVisibleResultData, "", "80%", "", "", ""));
}
private int staticIntField(String name) throws Exception {
Field field = SimilarAsinTaskService.class.getDeclaredField(name);
field.setAccessible(true);
return field.getInt(null);
}
}
@@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -33,6 +34,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
@@ -83,7 +85,10 @@ class TaskHeartbeatServiceTest {
assertTrue(result.isAlive());
assertEquals(PublishTaskService.MODULE_TYPE, result.getModuleType());
verify(fileTaskMapper).update(isNull(), any(LambdaUpdateWrapper.class));
InOrder routingBeforeUpdate = inOrder(publishTaskService, fileTaskMapper);
routingBeforeUpdate.verify(publishTaskService)
.ensureTaskOwnedByCurrentInstance(task, "publish task heartbeat");
routingBeforeUpdate.verify(fileTaskMapper).update(isNull(), any(LambdaUpdateWrapper.class));
verify(publishTaskService).touchHeartbeat(taskId, request);
}
}
@@ -93,13 +93,14 @@ class TaskResultFileJobWorkerTest {
job.setTaskId(taskId);
job.setResultId(resultId);
job.setModuleType(PublishTaskService.MODULE_TYPE);
job.setScopeKey("publish:20141");
job.setScopeKey("task:20141:owner:instance-a");
FileResultEntity result = new FileResultEntity();
result.setResultFileUrl("result/publish/20141.xlsx");
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
when(taskFileJobService.markRunning(jobId)).thenReturn(true);
when(instanceMetadata.getInstanceId()).thenReturn("instance-a");
when(taskDistributedLockService.acquire(
PublishTaskService.MODULE_TYPE,
taskId,
@@ -112,4 +113,19 @@ class TaskResultFileJobWorkerTest {
verify(taskFileJobService).markSuccess(job, "result/publish/20141.xlsx");
verifyNoInteractions(taskResultPayloadService);
}
@Test
void publishFileJobOwnedByAnotherInstanceIsNotClaimed() {
TaskFileJobEntity job = new TaskFileJobEntity();
job.setId(13642L);
job.setTaskId(20142L);
job.setResultId(22930L);
job.setModuleType(PublishTaskService.MODULE_TYPE);
job.setScopeKey("task:20142:owner:instance-b");
when(instanceMetadata.getInstanceId()).thenReturn("instance-a");
worker.process(job);
verifyNoInteractions(taskFileJobService, taskDistributedLockService, publishTaskService);
}
}