上架需求增加
This commit is contained in:
+354
@@ -0,0 +1,354 @@
|
||||
package com.nanri.aiimage.modules.publish.service;
|
||||
|
||||
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.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.PublishResultFileDto;
|
||||
import com.nanri.aiimage.modules.publish.model.dto.PublishRowDto;
|
||||
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;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileResultMapper;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileResultEntity;
|
||||
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 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.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PublishTaskServiceTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
|
||||
TableInfoHelper.initTableInfo(assistant, FileTaskEntity.class);
|
||||
TableInfoHelper.initTableInfo(assistant, FileResultEntity.class);
|
||||
TableInfoHelper.initTableInfo(assistant, PublishFileEntity.class);
|
||||
TableInfoHelper.initTableInfo(assistant, PublishItemEntity.class);
|
||||
}
|
||||
|
||||
@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 TaskFileJobService taskFileJobService;
|
||||
@Mock private TaskDistributedLockService taskDistributedLockService;
|
||||
@Mock private OssStorageService ossStorageService;
|
||||
@Mock private ObjectMapper objectMapper;
|
||||
@Mock private TransactionTemplate transactionTemplate;
|
||||
|
||||
@InjectMocks private PublishTaskService service;
|
||||
|
||||
@BeforeEach
|
||||
void executeTransactionsInline() {
|
||||
lenient().doAnswer(invocation -> {
|
||||
Consumer<TransactionStatus> callback = invocation.getArgument(0);
|
||||
callback.accept(null);
|
||||
return null;
|
||||
}).when(transactionTemplate).executeWithoutResult(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resultCallbackRejectsAnotherUserBeforeReadingOrReplacingRows() {
|
||||
long taskId = 101L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishSubmitResultRequest request = resultRequest(8L, 201L, List.of(row("1")));
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
|
||||
BusinessException error = assertThrows(BusinessException.class,
|
||||
() -> service.submitResult(taskId, request));
|
||||
|
||||
assertEquals("任务不存在", error.getMessage());
|
||||
verifyNoInteractions(publishItemMapper);
|
||||
verify(publishFileMapper, never()).updateById(any(PublishFileEntity.class));
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resultCallbackRejectsIncompleteRowsWithoutDeletingOriginalData() {
|
||||
long taskId = 102L;
|
||||
long fileId = 202L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishFileEntity file = file(taskId, fileId, "RUNNING", "郭亚庆.xlsx");
|
||||
PublishSubmitResultRequest request = resultRequest(7L, fileId, List.of(row("1")));
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectById(fileId)).thenReturn(file);
|
||||
when(publishItemMapper.selectCount(any())).thenReturn(2L);
|
||||
|
||||
BusinessException error = assertThrows(BusinessException.class,
|
||||
() -> service.submitResult(taskId, request));
|
||||
|
||||
assertTrue(error.getMessage().contains("回传数据不完整"));
|
||||
assertEquals("RUNNING", file.getStatus());
|
||||
verify(publishItemMapper, never()).delete(any());
|
||||
verify(publishItemMapper, never()).insertBatch(any());
|
||||
verify(publishFileMapper, never()).updateById(any(PublishFileEntity.class));
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resultCallbackRejectsBlankObjectWithoutDeletingOriginalData() {
|
||||
long taskId = 103L;
|
||||
long fileId = 203L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishFileEntity file = file(taskId, fileId, "RUNNING", "郭亚庆.xlsx");
|
||||
PublishSubmitResultRequest request = resultRequest(7L, fileId, List.of(new PublishRowDto()));
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectById(fileId)).thenReturn(file);
|
||||
|
||||
BusinessException error = assertThrows(BusinessException.class,
|
||||
() -> service.submitResult(taskId, request));
|
||||
|
||||
assertTrue(error.getMessage().contains("空白对象行"));
|
||||
verify(publishItemMapper, never()).delete(any());
|
||||
verify(publishItemMapper, never()).insertBatch(any());
|
||||
verify(publishFileMapper, never()).updateById(any(PublishFileEntity.class));
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void resultCallbackCanDeriveTaskOwnerWhenUserIdIsOmitted() {
|
||||
long taskId = 108L;
|
||||
long fileId = 208L;
|
||||
long resultId = 308L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishFileEntity file = file(taskId, fileId, "RUNNING", "郭亚庆.xlsx");
|
||||
FileResultEntity result = new FileResultEntity();
|
||||
result.setId(resultId);
|
||||
result.setTaskId(taskId);
|
||||
result.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
result.setSuccess(0);
|
||||
PublishSubmitResultRequest request = resultRequest(7L, fileId, List.of(row("1")));
|
||||
request.setUserId(null);
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectById(fileId)).thenReturn(file);
|
||||
when(publishItemMapper.selectCount(any())).thenReturn(1L);
|
||||
when(publishFileMapper.selectList(any())).thenReturn(List.of(file));
|
||||
when(fileResultMapper.selectOne(any())).thenReturn(result);
|
||||
|
||||
service.submitResult(taskId, request);
|
||||
|
||||
assertEquals("SUCCESS", file.getStatus());
|
||||
verify(taskFileJobService).enqueueAssembleResult(
|
||||
taskId, PublishTaskService.MODULE_TYPE, resultId, "task:" + taskId);
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void activateFileRejectsSecondRunningFileWhileHoldingTaskLock() {
|
||||
long taskId = 104L;
|
||||
long fileId = 204L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishFileEntity target = file(taskId, fileId, "PENDING", "待执行.xlsx");
|
||||
PublishFileEntity running = file(taskId, 205L, "RUNNING", "执行中.xlsx");
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectById(fileId)).thenReturn(target);
|
||||
when(publishFileMapper.selectOne(any())).thenReturn(running);
|
||||
|
||||
BusinessException error = assertThrows(BusinessException.class,
|
||||
() -> service.activateFile(taskId, fileId, 7L));
|
||||
|
||||
assertTrue(error.getMessage().contains("已有文件正在执行"));
|
||||
verify(publishFileMapper, never()).update(isNull(), any());
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void activateFileIsIdempotentForTheSameRunningFile() {
|
||||
long taskId = 105L;
|
||||
long fileId = 205L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
PublishFileEntity running = file(taskId, fileId, "RUNNING", "执行中.xlsx");
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId)).thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectById(fileId)).thenReturn(running);
|
||||
|
||||
service.activateFile(taskId, fileId, 7L);
|
||||
|
||||
verify(publishFileMapper, never()).selectOne(any());
|
||||
verify(publishFileMapper, never()).update(isNull(), any());
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staleTaskWithSuccessfulFileFailsRemainderAndEnqueuesAssembly() {
|
||||
long taskId = 106L;
|
||||
long resultId = 306L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
task.setSourceFileCount(2);
|
||||
task.setUpdatedAt(LocalDateTime.now().minusHours(1));
|
||||
task.setErrorMessage("旧错误");
|
||||
PublishFileEntity success = file(taskId, 206L, "SUCCESS", "成功.xlsx");
|
||||
PublishFileEntity failed = file(taskId, 207L, "FAILED", "超时.xlsx");
|
||||
FileResultEntity result = new FileResultEntity();
|
||||
result.setId(resultId);
|
||||
result.setTaskId(taskId);
|
||||
result.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
result.setSuccess(0);
|
||||
result.setErrorMessage("旧错误");
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(fileTaskMapper.selectList(any())).thenReturn(List.of(task));
|
||||
when(taskFileJobService.countUnfinishedAssembleJobs(taskId, PublishTaskService.MODULE_TYPE))
|
||||
.thenReturn(0L);
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId, 0L))
|
||||
.thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectList(any())).thenReturn(List.of(success, failed));
|
||||
when(fileResultMapper.selectOne(any())).thenReturn(result);
|
||||
|
||||
service.failStaleTasks();
|
||||
|
||||
assertEquals("RUNNING", task.getStatus());
|
||||
assertEquals(1, task.getSuccessFileCount());
|
||||
assertEquals(1, task.getFailedFileCount());
|
||||
assertNull(task.getErrorMessage());
|
||||
assertNull(task.getFinishedAt());
|
||||
assertEquals(0, result.getSuccess());
|
||||
assertNull(result.getErrorMessage());
|
||||
assertNotNull(task.getUpdatedAt());
|
||||
verify(taskFileJobService, times(2))
|
||||
.countUnfinishedAssembleJobs(taskId, PublishTaskService.MODULE_TYPE);
|
||||
verify(taskFileJobService).enqueueAssembleResult(
|
||||
taskId, PublishTaskService.MODULE_TYPE, resultId, "task:" + taskId);
|
||||
verify(fileTaskMapper).updateById(task);
|
||||
verify(fileResultMapper).updateById(result);
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void staleTaskWithoutSuccessfulFilesFailsTaskAndResult() {
|
||||
long taskId = 107L;
|
||||
long resultId = 307L;
|
||||
FileTaskEntity task = task(taskId, 7L, "RUNNING");
|
||||
task.setSourceFileCount(1);
|
||||
task.setUpdatedAt(LocalDateTime.now().minusHours(1));
|
||||
PublishFileEntity failed = file(taskId, 208L, "FAILED", "超时.xlsx");
|
||||
FileResultEntity result = new FileResultEntity();
|
||||
result.setId(resultId);
|
||||
result.setTaskId(taskId);
|
||||
result.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
result.setSuccess(0);
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(fileTaskMapper.selectList(any())).thenReturn(List.of(task));
|
||||
when(taskFileJobService.countUnfinishedAssembleJobs(taskId, PublishTaskService.MODULE_TYPE))
|
||||
.thenReturn(0L);
|
||||
when(taskDistributedLockService.acquire(PublishTaskService.MODULE_TYPE, taskId, 0L))
|
||||
.thenReturn(lock);
|
||||
when(fileTaskMapper.selectById(taskId)).thenReturn(task);
|
||||
when(publishFileMapper.selectList(any())).thenReturn(List.of(failed));
|
||||
when(fileResultMapper.selectOne(any())).thenReturn(result);
|
||||
|
||||
service.failStaleTasks();
|
||||
|
||||
assertEquals("FAILED", task.getStatus());
|
||||
assertEquals(0, task.getSuccessFileCount());
|
||||
assertEquals(1, task.getFailedFileCount());
|
||||
assertEquals("任务心跳超时", task.getErrorMessage());
|
||||
assertNotNull(task.getFinishedAt());
|
||||
assertEquals(0, result.getSuccess());
|
||||
assertEquals("任务心跳超时", result.getErrorMessage());
|
||||
verify(taskFileJobService, never()).enqueueAssembleResult(
|
||||
taskId, PublishTaskService.MODULE_TYPE, resultId, "task:" + taskId);
|
||||
verify(fileTaskMapper).updateById(task);
|
||||
verify(fileResultMapper).updateById(result);
|
||||
verify(lock).close();
|
||||
}
|
||||
|
||||
private FileTaskEntity task(long taskId, long userId, String status) {
|
||||
FileTaskEntity task = new FileTaskEntity();
|
||||
task.setId(taskId);
|
||||
task.setUserId(userId);
|
||||
task.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
task.setStatus(status);
|
||||
return task;
|
||||
}
|
||||
|
||||
private PublishFileEntity file(long taskId,
|
||||
long fileId,
|
||||
String status,
|
||||
String sourceFilename) {
|
||||
PublishFileEntity file = new PublishFileEntity();
|
||||
file.setId(fileId);
|
||||
file.setTaskId(taskId);
|
||||
file.setStatus(status);
|
||||
file.setSourceFilename(sourceFilename);
|
||||
return file;
|
||||
}
|
||||
|
||||
private PublishSubmitResultRequest resultRequest(long userId,
|
||||
long fileId,
|
||||
List<PublishRowDto> rows) {
|
||||
PublishResultFileDto file = new PublishResultFileDto();
|
||||
file.setFileId(fileId);
|
||||
file.setRows(rows);
|
||||
PublishSubmitResultRequest request = new PublishSubmitResultRequest();
|
||||
request.setUserId(userId);
|
||||
request.setFiles(List.of(file));
|
||||
return request;
|
||||
}
|
||||
|
||||
private PublishRowDto row(String sourceId) {
|
||||
PublishRowDto row = new PublishRowDto();
|
||||
row.setSourceId(sourceId);
|
||||
row.setAsin("B001");
|
||||
row.setCountry("德国");
|
||||
return row;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.nanri.aiimage.modules.publish.service;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.publish.model.dto.PublishRowDto;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
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.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PublishWorkbookServiceTest {
|
||||
|
||||
private final PublishWorkbookService service = new PublishWorkbookService();
|
||||
|
||||
@Test
|
||||
void parsesOnlyTheExactEightHeadersAcrossNonEmptySheets() 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");
|
||||
workbook.createSheet("空白页");
|
||||
writeSourceSheet(workbook.createSheet("德国数据"), "DE", "B002");
|
||||
workbook.write(output);
|
||||
}
|
||||
|
||||
PublishWorkbookService.ParsedWorkbook parsed = service.parse(valid);
|
||||
assertEquals(2, parsed.rows().size());
|
||||
assertEquals("B001", parsed.rows().get(0).getAsin());
|
||||
assertEquals("DE", parsed.rows().get(1).getCountry());
|
||||
|
||||
File invalid = directory.resolve("invalid.xlsx").toFile();
|
||||
try (Workbook workbook = new XSSFWorkbook();
|
||||
FileOutputStream output = new FileOutputStream(invalid)) {
|
||||
Sheet sheet = workbook.createSheet("错误表头");
|
||||
Row header = sheet.createRow(0);
|
||||
List<String> headers = new ArrayList<>(PublishWorkbookService.HEADERS);
|
||||
headers.set(1, "Asin");
|
||||
for (int index = 0; index < headers.size(); index++) {
|
||||
header.createCell(index).setCellValue(headers.get(index));
|
||||
}
|
||||
workbook.write(output);
|
||||
}
|
||||
assertThrows(BusinessException.class, () -> service.parse(invalid));
|
||||
} finally {
|
||||
FileUtil.del(directory.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void writesOneSheetPerNormalizedCountryWithExactHeaders() throws Exception {
|
||||
Path directory = Files.createTempDirectory("publish-sheets-");
|
||||
try {
|
||||
File output = directory.resolve("result.xlsx").toFile();
|
||||
service.writeWorkbook(output, List.of(
|
||||
row("1", "B001", "UK", "19.99"),
|
||||
row("2", "B002", "德国", "not-a-number"),
|
||||
row("3", "B003", "GB", "20")));
|
||||
|
||||
try (FileInputStream input = new FileInputStream(output);
|
||||
Workbook workbook = new XSSFWorkbook(input)) {
|
||||
assertEquals(2, workbook.getNumberOfSheets());
|
||||
assertEquals(Set.of("英国", "德国"),
|
||||
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),
|
||||
uk.getRow(0).getCell(index).getStringCellValue());
|
||||
}
|
||||
assertEquals(CellType.NUMERIC, uk.getRow(1).getCell(4).getCellType());
|
||||
assertEquals(19.99D, uk.getRow(1).getCell(4).getNumericCellValue(), 0.0001D);
|
||||
assertEquals("not-a-number",
|
||||
workbook.getSheet("德国").getRow(1).getCell(4).getStringCellValue());
|
||||
}
|
||||
} finally {
|
||||
FileUtil.del(directory.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void packagingDecisionUsesOriginalSourceFileCount() throws Exception {
|
||||
Path directory = Files.createTempDirectory("publish-package-");
|
||||
try {
|
||||
List<PublishWorkbookService.WorkbookInput> oneSuccess = List.of(
|
||||
new PublishWorkbookService.WorkbookInput(
|
||||
"郭亚庆.xlsx", "郭亚庆", List.of(row("1", "B001", "英国", "50"))));
|
||||
|
||||
PublishWorkbookService.PackagedResult single = service.packageTaskResult(
|
||||
directory.resolve("single").toFile(), "PUBLISH-1", 1, oneSuccess);
|
||||
assertTrue(single.filename().endsWith(".xlsx"));
|
||||
assertEquals(PublishWorkbookService.XLSX_CONTENT_TYPE, single.contentType());
|
||||
|
||||
PublishWorkbookService.PackagedResult multiWithOneSuccess = service.packageTaskResult(
|
||||
directory.resolve("multi").toFile(), "PUBLISH-2", 2, oneSuccess);
|
||||
assertTrue(multiWithOneSuccess.filename().endsWith(".zip"));
|
||||
assertEquals(PublishWorkbookService.ZIP_CONTENT_TYPE, multiWithOneSuccess.contentType());
|
||||
try (ZipFile zip = new ZipFile(multiWithOneSuccess.file())) {
|
||||
assertEquals(1, zip.size());
|
||||
assertTrue(zip.entries().nextElement().getName().endsWith(".xlsx"));
|
||||
}
|
||||
} finally {
|
||||
FileUtil.del(directory.toFile());
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
Row data = sheet.createRow(1);
|
||||
data.createCell(0).setCellValue("1");
|
||||
data.createCell(1).setCellValue(asin);
|
||||
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) {
|
||||
PublishRowDto row = new PublishRowDto();
|
||||
row.setSourceId(id);
|
||||
row.setAsin(asin);
|
||||
row.setCountry(country);
|
||||
row.setBrand("Brand");
|
||||
row.setPrice(price);
|
||||
row.setStatus("成功");
|
||||
row.setSyncStatus("成功");
|
||||
row.setSyncCountries("德国,法国");
|
||||
return row;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
package com.nanri.aiimage.modules.task.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.modules.appearancepatent.service.AppearancePatentTaskCacheService;
|
||||
import com.nanri.aiimage.modules.brand.mapper.BrandCrawlTaskMapper;
|
||||
import com.nanri.aiimage.modules.brand.service.BrandTaskProgressCacheService;
|
||||
import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandTaskCacheService;
|
||||
import com.nanri.aiimage.modules.patroldelete.service.PatrolDeleteTaskCacheService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskCacheService;
|
||||
import com.nanri.aiimage.modules.productrisk.service.ProductRiskTaskCacheService;
|
||||
import com.nanri.aiimage.modules.publish.service.PublishTaskService;
|
||||
import com.nanri.aiimage.modules.queryasin.service.QueryAsinTaskCacheService;
|
||||
import com.nanri.aiimage.modules.shopmatch.service.ShopMatchTaskCacheService;
|
||||
import com.nanri.aiimage.modules.similarasin.service.SimilarAsinTaskCacheService;
|
||||
import com.nanri.aiimage.modules.task.mapper.FileTaskMapper;
|
||||
import com.nanri.aiimage.modules.task.model.dto.TaskHeartbeatRequest;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import com.nanri.aiimage.modules.task.model.vo.TaskHeartbeatVo;
|
||||
import com.nanri.aiimage.modules.withdraw.service.WithdrawTaskCacheService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
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.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class TaskHeartbeatServiceTest {
|
||||
|
||||
@BeforeAll
|
||||
static void initializeMybatisMetadata() {
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
FileTaskEntity.class);
|
||||
}
|
||||
|
||||
@Mock private FileTaskMapper fileTaskMapper;
|
||||
@Mock private BrandCrawlTaskMapper brandCrawlTaskMapper;
|
||||
@Mock private ProductRiskTaskCacheService productRiskTaskCacheService;
|
||||
@Mock private PublishTaskService publishTaskService;
|
||||
@Mock private PriceTrackTaskCacheService priceTrackTaskCacheService;
|
||||
@Mock private ShopMatchTaskCacheService shopMatchTaskCacheService;
|
||||
@Mock private PatrolDeleteTaskCacheService patrolDeleteTaskCacheService;
|
||||
@Mock private QueryAsinTaskCacheService queryAsinTaskCacheService;
|
||||
@Mock private WithdrawTaskCacheService withdrawTaskCacheService;
|
||||
@Mock private AppearancePatentTaskCacheService appearancePatentTaskCacheService;
|
||||
@Mock private SimilarAsinTaskCacheService similarAsinTaskCacheService;
|
||||
@Mock private DeleteBrandTaskCacheService deleteBrandTaskCacheService;
|
||||
@Mock private BrandTaskProgressCacheService brandTaskProgressCacheService;
|
||||
|
||||
@InjectMocks private TaskHeartbeatService service;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void publishHeartbeatTouchesGenericTaskAndModuleProgress() {
|
||||
long taskId = 20142L;
|
||||
FileTaskEntity task = new FileTaskEntity();
|
||||
task.setId(taskId);
|
||||
task.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
task.setStatus("RUNNING");
|
||||
|
||||
TaskHeartbeatRequest request = new TaskHeartbeatRequest();
|
||||
request.setPhase("dispatching");
|
||||
request.setCurrent(20);
|
||||
request.setTotal(100);
|
||||
|
||||
when(fileTaskMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(task);
|
||||
when(brandCrawlTaskMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);
|
||||
when(fileTaskMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1);
|
||||
|
||||
TaskHeartbeatVo result = service.heartbeat(taskId, request);
|
||||
|
||||
assertTrue(result.isAlive());
|
||||
assertEquals(PublishTaskService.MODULE_TYPE, result.getModuleType());
|
||||
verify(fileTaskMapper).update(isNull(), any(LambdaUpdateWrapper.class));
|
||||
verify(publishTaskService).touchHeartbeat(taskId, request);
|
||||
}
|
||||
}
|
||||
+33
@@ -8,6 +8,7 @@ import com.nanri.aiimage.modules.deletebrand.service.DeleteBrandRunService;
|
||||
import com.nanri.aiimage.modules.patroldelete.service.PatrolDeleteTaskService;
|
||||
import com.nanri.aiimage.modules.pricetrack.service.PriceTrackTaskService;
|
||||
import com.nanri.aiimage.modules.productrisk.service.ProductRiskTaskService;
|
||||
import com.nanri.aiimage.modules.publish.service.PublishTaskService;
|
||||
import com.nanri.aiimage.modules.queryasin.service.QueryAsinTaskService;
|
||||
import com.nanri.aiimage.modules.shopmatch.service.ShopMatchTaskService;
|
||||
import com.nanri.aiimage.modules.similarasin.service.SimilarAsinTaskService;
|
||||
@@ -25,6 +26,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -39,6 +41,7 @@ class TaskResultFileJobWorkerTest {
|
||||
@Mock private ShopMatchTaskService shopMatchTaskService;
|
||||
@Mock private PriceTrackTaskService priceTrackTaskService;
|
||||
@Mock private ProductRiskTaskService productRiskTaskService;
|
||||
@Mock private PublishTaskService publishTaskService;
|
||||
@Mock private QueryAsinTaskService queryAsinTaskService;
|
||||
@Mock private WithdrawTaskService withdrawTaskService;
|
||||
@Mock private PatrolDeleteTaskService patrolDeleteTaskService;
|
||||
@@ -79,4 +82,34 @@ class TaskResultFileJobWorkerTest {
|
||||
order.verify(lock).close();
|
||||
order.verify(withdrawTaskService).tryFinalizeTask(taskId, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishFileSuccessDelegatesAndDoesNotRunGenericPayloadCleanup() {
|
||||
long jobId = 13641L;
|
||||
long taskId = 20141L;
|
||||
long resultId = 22929L;
|
||||
TaskFileJobEntity job = new TaskFileJobEntity();
|
||||
job.setId(jobId);
|
||||
job.setTaskId(taskId);
|
||||
job.setResultId(resultId);
|
||||
job.setModuleType(PublishTaskService.MODULE_TYPE);
|
||||
job.setScopeKey("publish:20141");
|
||||
|
||||
FileResultEntity result = new FileResultEntity();
|
||||
result.setResultFileUrl("result/publish/20141.xlsx");
|
||||
TaskDistributedLockService.LockHandle lock = mock(TaskDistributedLockService.LockHandle.class);
|
||||
|
||||
when(taskFileJobService.markRunning(jobId)).thenReturn(true);
|
||||
when(taskDistributedLockService.acquire(
|
||||
PublishTaskService.MODULE_TYPE,
|
||||
taskId,
|
||||
TaskDistributedLockService.DEFAULT_WAIT_MILLIS)).thenReturn(lock);
|
||||
when(fileResultMapper.selectById(resultId)).thenReturn(result);
|
||||
|
||||
worker.process(job);
|
||||
|
||||
verify(publishTaskService).processResultFileJob(job);
|
||||
verify(taskFileJobService).markSuccess(job, "result/publish/20141.xlsx");
|
||||
verifyNoInteractions(taskResultPayloadService);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user