更新任务存储、权限与货源查询流程
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
supernijia
2026-08-22 21:23:01 +08:00
parent 7a7f1dfa21
commit 159d52194c
32 changed files with 1239 additions and 69 deletions
@@ -0,0 +1,202 @@
package com.nanri.aiimage.modules.dedupe.controller;
import com.nanri.aiimage.common.exception.BusinessException;
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
import com.nanri.aiimage.modules.dedupe.service.DedupeTotalDataService;
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
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.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
class DedupeTotalDataControllerTest {
@Test
void exportReturnsStreamingBodyAndDelegatesToService() throws Exception {
DedupeTotalDataService service = mock(DedupeTotalDataService.class);
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
PermissionMenuService permissionMenuService = mock(PermissionMenuService.class);
HttpServletRequest request = mock(HttpServletRequest.class);
DedupeTotalDataController controller = new DedupeTotalDataController(
service, authSupport, permissionMenuService);
AdminUserEntity operator = new AdminUserEntity();
operator.setId(8L);
operator.setRole("super_admin");
when(authSupport.requireUser(request)).thenReturn(operator);
when(authSupport.currentRole(operator)).thenReturn("super_admin");
doAnswer(invocation -> {
OutputStream outputStream = invocation.getArgument(0);
outputStream.write(new byte[]{1, 2, 3});
return null;
}).when(service).writeExport(
any(OutputStream.class),
eq("member"),
eq(LocalDate.of(2026, 7, 1)),
eq(LocalDate.of(2026, 7, 31)),
eq(3L),
eq(8L));
ResponseEntity<StreamingResponseBody> response = controller.export(
"member",
LocalDate.of(2026, 7, 1),
LocalDate.of(2026, 7, 31),
3L,
request);
assertNotNull(response.getBody());
assertTrue(response.getBody() instanceof StreamingResponseBody);
assertEquals(MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
response.getHeaders().getContentType());
assertNotNull(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION));
assertTrue(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION)
.contains("dedupe-total-data-"));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
response.getBody().writeTo(outputStream);
assertArrayEquals(new byte[]{1, 2, 3}, outputStream.toByteArray());
verify(service).writeExport(
any(OutputStream.class),
eq("member"),
eq(LocalDate.of(2026, 7, 1)),
eq(LocalDate.of(2026, 7, 31)),
eq(3L),
eq(8L));
verify(service, never()).writeMonthlyZipExport(
any(OutputStream.class), any(), any(), any(), any(), any());
verifyNoInteractions(permissionMenuService);
}
@Test
void exportWithIncompleteDatesStaysSingleXlsx() throws Exception {
DedupeTotalDataService service = mock(DedupeTotalDataService.class);
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
PermissionMenuService permissionMenuService = mock(PermissionMenuService.class);
HttpServletRequest request = mock(HttpServletRequest.class);
DedupeTotalDataController controller = new DedupeTotalDataController(
service, authSupport, permissionMenuService);
AdminUserEntity operator = new AdminUserEntity();
operator.setId(8L);
operator.setRole("super_admin");
when(authSupport.requireUser(request)).thenReturn(operator);
when(authSupport.currentRole(operator)).thenReturn("super_admin");
ResponseEntity<StreamingResponseBody> response = controller.export(
"member",
null,
LocalDate.of(2026, 8, 2),
3L,
request);
assertEquals(MediaType.parseMediaType(
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"),
response.getHeaders().getContentType());
assertTrue(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION)
.contains(".xlsx"));
assertNotNull(response.getBody());
response.getBody().writeTo(new ByteArrayOutputStream());
verify(service).writeExport(
any(OutputStream.class),
eq("member"),
isNull(),
eq(LocalDate.of(2026, 8, 2)),
eq(3L),
eq(8L));
verify(service, never()).writeMonthlyZipExport(
any(OutputStream.class), any(), any(), any(), any(), any());
}
@Test
void crossMonthExportReturnsZipAndDelegatesMonthlyExport() throws Exception {
DedupeTotalDataService service = mock(DedupeTotalDataService.class);
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
PermissionMenuService permissionMenuService = mock(PermissionMenuService.class);
HttpServletRequest request = mock(HttpServletRequest.class);
DedupeTotalDataController controller = new DedupeTotalDataController(
service, authSupport, permissionMenuService);
AdminUserEntity operator = new AdminUserEntity();
operator.setId(8L);
operator.setRole("super_admin");
when(authSupport.requireUser(request)).thenReturn(operator);
when(authSupport.currentRole(operator)).thenReturn("super_admin");
doAnswer(invocation -> {
OutputStream outputStream = invocation.getArgument(0);
outputStream.write(new byte[]{4, 5});
return null;
}).when(service).writeMonthlyZipExport(
any(OutputStream.class),
eq("member"),
eq(LocalDate.of(2026, 7, 15)),
eq(LocalDate.of(2026, 8, 2)),
eq(3L),
eq(8L));
ResponseEntity<StreamingResponseBody> response = controller.export(
"member",
LocalDate.of(2026, 7, 15),
LocalDate.of(2026, 8, 2),
3L,
request);
assertEquals(MediaType.parseMediaType("application/zip"), response.getHeaders().getContentType());
assertTrue(response.getHeaders().getFirst(HttpHeaders.CONTENT_DISPOSITION)
.contains(".zip"));
assertNotNull(response.getBody());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
response.getBody().writeTo(outputStream);
assertArrayEquals(new byte[]{4, 5}, outputStream.toByteArray());
verify(service).writeMonthlyZipExport(
any(OutputStream.class),
eq("member"),
eq(LocalDate.of(2026, 7, 15)),
eq(LocalDate.of(2026, 8, 2)),
eq(3L),
eq(8L));
verify(service, never()).writeExport(any(OutputStream.class), any(), any(), any(), any(), any());
}
@Test
void reversedDateRangeIsRejectedBeforeStreamingBodyIsReturned() {
DedupeTotalDataService service = mock(DedupeTotalDataService.class);
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
PermissionMenuService permissionMenuService = mock(PermissionMenuService.class);
HttpServletRequest request = mock(HttpServletRequest.class);
DedupeTotalDataController controller = new DedupeTotalDataController(
service, authSupport, permissionMenuService);
BusinessException exception = assertThrows(BusinessException.class, () -> controller.export(
"member",
LocalDate.of(2026, 8, 2),
LocalDate.of(2026, 7, 15),
3L,
request));
assertEquals(400, exception.getCode());
verifyNoInteractions(service, authSupport, permissionMenuService);
}
}
@@ -33,9 +33,13 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -312,6 +316,75 @@ class DedupeTotalDataServiceTest {
verify(dedupeTotalDataMapper).selectList(any());
}
@Test
void writeExportPagesThroughLargeResultSet() throws Exception {
when(adminUserMapper.selectById(1L)).thenReturn(user(1L, "super_admin", "root"));
when(dedupeTotalDataMapper.selectList(any()))
.thenReturn(exportBatch(5000L, 2000), List.of(data(3000L, 23L)));
ByteArrayOutputStream output = new ByteArrayOutputStream();
service.writeExport(output, "", null, null, null, 1L);
verify(dedupeTotalDataMapper, times(2)).selectList(any());
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(output.toByteArray()))) {
var sheet = workbook.getSheetAt(0);
assertEquals(2001, sheet.getLastRowNum());
assertEquals("5000", sheet.getRow(1).getCell(0).getStringCellValue());
assertEquals("3000", sheet.getRow(2001).getCell(0).getStringCellValue());
}
}
@Test
@SuppressWarnings({"rawtypes", "unchecked"})
void writeMonthlyZipExportSplitsByMonthAndClipsDateRanges() throws Exception {
TableInfoHelper.initTableInfo(
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
DedupeTotalDataEntity.class);
when(adminUserMapper.selectById(1L)).thenReturn(user(1L, "super_admin", "root"));
when(shopManageGroupMapper.selectById(3L)).thenReturn(group(3L));
DedupeTotalDataEntity july = data(500L, 23L);
july.setDataValue("JULY");
july.setCreatedAt(LocalDateTime.of(2026, 7, 31, 10, 0));
DedupeTotalDataEntity august = data(400L, 23L);
august.setDataValue("AUGUST");
august.setCreatedAt(LocalDateTime.of(2026, 8, 1, 10, 0));
when(dedupeTotalDataMapper.selectList(any()))
.thenReturn(List.of(july), List.of(august));
ByteArrayOutputStream output = new ByteArrayOutputStream();
service.writeMonthlyZipExport(
output,
" member ",
LocalDate.of(2026, 7, 15),
LocalDate.of(2026, 8, 2),
3L,
1L);
Map<String, byte[]> entries = unzip(output.toByteArray());
assertEquals(List.of(
"dedupe-total-data-2026-07.xlsx",
"dedupe-total-data-2026-08.xlsx"), new ArrayList<>(entries.keySet()));
assertWorkbookDataValue(entries.get("dedupe-total-data-2026-07.xlsx"), "JULY");
assertWorkbookDataValue(entries.get("dedupe-total-data-2026-08.xlsx"), "AUGUST");
ArgumentCaptor<LambdaQueryWrapper<DedupeTotalDataEntity>> queryCaptor =
ArgumentCaptor.forClass((Class) LambdaQueryWrapper.class);
verify(dedupeTotalDataMapper, times(2)).selectList(queryCaptor.capture());
List<LambdaQueryWrapper<DedupeTotalDataEntity>> queries = queryCaptor.getAllValues();
assertQueryContainsText(queries.get(0), "member");
assertQueryContains(queries.get(0),
LocalDate.of(2026, 7, 15).atStartOfDay(),
LocalDate.of(2026, 8, 1).atStartOfDay(),
3L);
assertQueryContainsText(queries.get(1), "member");
assertQueryContains(queries.get(1),
LocalDate.of(2026, 8, 1).atStartOfDay(),
LocalDate.of(2026, 8, 3).atStartOfDay(),
3L);
verify(adminUserMapper).selectById(1L);
verify(shopManageGroupMapper).selectById(3L);
}
@Test
void exportRejectsReversedDateRange() {
assertThrows(BusinessException.class, () -> service.export(
@@ -404,6 +477,48 @@ class DedupeTotalDataServiceTest {
return entity;
}
private List<DedupeTotalDataEntity> exportBatch(long startId, int count) {
List<DedupeTotalDataEntity> rows = new ArrayList<>(count);
for (long id = startId; id > startId - count; id--) {
rows.add(data(id, 23L));
}
return rows;
}
private Map<String, byte[]> unzip(byte[] bytes) throws Exception {
Map<String, byte[]> entries = new LinkedHashMap<>();
try (ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(bytes))) {
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
ByteArrayOutputStream entryOutput = new ByteArrayOutputStream();
zipInputStream.transferTo(entryOutput);
entries.put(entry.getName(), entryOutput.toByteArray());
zipInputStream.closeEntry();
}
}
return entries;
}
private void assertWorkbookDataValue(byte[] bytes, String expectedValue) throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) {
assertEquals(expectedValue, workbook.getSheetAt(0).getRow(1).getCell(1).getStringCellValue());
}
}
private void assertQueryContains(LambdaQueryWrapper<DedupeTotalDataEntity> query, Object... values) {
query.getSqlSegment();
for (Object value : values) {
assertTrue(query.getParamNameValuePairs().containsValue(value));
}
}
private void assertQueryContainsText(LambdaQueryWrapper<DedupeTotalDataEntity> query, String expectedText) {
query.getSqlSegment();
assertTrue(query.getParamNameValuePairs().values().stream()
.map(String::valueOf)
.anyMatch(value -> value.contains(expectedText)));
}
private void stubWritableGroup(Long operatorId, Long groupId) {
when(shopManageGroupMapper.selectAccessibleGroupIds(operatorId)).thenReturn(List.of(groupId));
when(shopManageGroupMapper.selectUserIdsByGroupIds(List.of(groupId))).thenReturn(List.of(operatorId));