完成上架相关优化、oss迁移
This commit is contained in:
+121
-9
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+77
-2
@@ -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();
|
||||
|
||||
+20
-10
@@ -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) {
|
||||
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user