Files
baiyue-firstleg/firstleg/src/test/java/com/baiyue/firstleg/service/InboundCartonLabelPrintServiceTest.java
T
2026-09-14 15:33:29 +08:00

268 lines
14 KiB
Java

package com.baiyue.firstleg.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.client.PrintPdfClient;
import com.baiyue.firstleg.dto.InboundCartonLabels;
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.Pallet;
import com.baiyue.firstleg.dto.DropshipPalletizationDtos.SkuItem;
import com.baiyue.firstleg.mapper.DropshipPalletizationMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.client.circuitbreaker.NoFallbackAvailableException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
import java.math.BigDecimal;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
class InboundCartonLabelPrintServiceTest {
private final InboundCartonLabelService labels = mock(InboundCartonLabelService.class);
private final PrintPdfClient print = mock(PrintPdfClient.class);
private final DropshipPalletizationMapper palletization = mock(DropshipPalletizationMapper.class);
private final IamCustomerSnapshotService customerSnapshots = mock(IamCustomerSnapshotService.class);
private InboundCartonLabelPrintService service;
@BeforeEach
void setUp() {
service = new InboundCartonLabelPrintService(labels, print, new ObjectMapper());
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
ReflectionTestUtils.setField(service, "palletizationMapper", palletization);
ReflectionTestUtils.setField(service, "customerSnapshots", customerSnapshots);
}
@Test
void dropshipPalletLabelIsAvailableBeforeSeal() {
Pallet open = dropshipPallet("OPEN");
stubPalletFacts(90001L, 1, 2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("request-open"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {7, 8, 9}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateDropshipPallet(open, "PCHK2608040174", "request-open");
assertThat(response.getBody()).containsExactly(7, 8, 9);
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
org.mockito.ArgumentMatchers.eq("request-open"), request.capture());
assertThat(request.getValue().templateCode()).isEqualTo("DROPSHIP_PALLET_A4_V2");
assertThat(request.getValue().pageSpec().widthMm()).isEqualByComparingTo("210");
assertThat(request.getValue().pageSpec().heightMm()).isEqualByComparingTo("297");
assertThat(request.getValue().documents()).singleElement().satisfies(document -> {
assertThat(document.palletNo()).isEqualTo("DP-260911-0001");
assertThat(document.destinationWarehouse()).isEqualTo("纽约 N.JDF");
assertThat(document.palletSequence()).isEqualTo(1);
assertThat(document.totalPallets()).isEqualTo(2);
assertThat(document.inboundRows()).singleElement().satisfies(row -> {
assertThat(row.inboundCode()).isEqualTo("PCHK2608040174");
assertThat(row.customerNickname()).isEqualTo("小虫科技");
assertThat(row.cartonCount()).isEqualTo(1);
assertThat(row.itemQuantity()).isEqualTo(19);
});
assertThat(document.skuRows()).singleElement().satisfies(row -> {
assertThat(row.skuCode()).isEqualTo("SKU-1");
assertThat(row.cartonCount()).isEqualTo(1);
assertThat(row.itemQuantity()).isEqualTo(19);
assertThat(row.unitsPerCarton()).isEqualTo("19");
});
});
}
@Test
void multiplePreGeneratedPalletsAreReturnedInOnePdf() {
Pallet first = dropshipPallet("OPEN");
Pallet second = new Pallet(90002L, "DP-260911-0002", "OPEN", 0, 0, 0,
BigDecimal.ZERO, BigDecimal.ZERO, 1, List.of(), List.of(), null);
stubPalletFacts(90001L, 1, 2);
stubPalletFacts(90002L, 2, 2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("request-all"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {4, 5, 6}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateDropshipPallets(
List.of(first, second), "PCHK2608040174", "request-all");
ArgumentCaptor<PrintPdfClient.DropshipPalletA4PdfRequest> request =
ArgumentCaptor.forClass(PrintPdfClient.DropshipPalletA4PdfRequest.class);
verify(print).generateDropshipPalletA4Documents(org.mockito.ArgumentMatchers.eq("internal-token"),
org.mockito.ArgumentMatchers.eq("request-all"), request.capture());
assertThat(request.getValue().documents()).extracting("palletNo")
.containsExactly("DP-260911-0001", "DP-260911-0002");
assertThat(request.getValue().downloadFileName()).contains("2托");
assertThat(response.getBody()).containsExactly(4, 5, 6);
}
@Test
void forwardsStableLabelsAndOnlySafePdfHeaders() {
when(labels.list(10001L, 70001L, List.of(1))).thenReturn(batch());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", "labels.pdf");
headers.set("X-Print-Page-Count", "1");
headers.set("X-Internal-Service-Token", "must-not-leak");
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.anyString(), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generate(10001L, 70001L, List.of(1), "request-1");
ArgumentCaptor<PrintPdfClient.InboundMarkPdfRequest> request =
ArgumentCaptor.forClass(PrintPdfClient.InboundMarkPdfRequest.class);
verify(print).generateInboundMarks(org.mockito.ArgumentMatchers.eq("internal-token"),
org.mockito.ArgumentMatchers.eq("request-1"), request.capture());
assertThat(request.getValue().downloadFileName()).isEqualTo("PCHK2608040174-19件-箱唛.pdf");
assertThat(request.getValue().labels()).singleElement().satisfies(label -> {
assertThat(label.qrContent()).isEqualTo("PCHK2608040174#19-1");
assertThat(label.cartonText()).isEqualTo("CTNS:19-1");
});
assertThat(response.getBody()).containsExactly(1, 2, 3);
assertThat(response.getHeaders().getFirst("X-Print-Page-Count")).isEqualTo("1");
assertThat(response.getHeaders()).doesNotContainKey("X-Internal-Service-Token");
}
@Test
void convertsPrintJsonBusinessFailureInsteadOfReturningItAsPdf() throws Exception {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
byte[] error = "{\"code\":\"A0422\",\"message\":\"标签字段超出模板限制\","
.concat("\"data\":null,\"requestId\":\"req-2\",\"traceId\":\"trace-2\"}")
.getBytes(java.nio.charset.StandardCharsets.UTF_8);
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
.thenReturn(ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(error));
assertThatThrownBy(() -> service.generate(10001L, 70001L, null, null))
.isInstanceOf(BizException.class)
.hasMessageContaining("标签字段超出模板限制")
.extracting(value -> ((BizException) value).getResultCode())
.isEqualTo(ResultCode.VALIDATION_FAILED);
}
@Test
void convertsCircuitBreakerFailureToServiceUnavailable() {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.isNull(), org.mockito.ArgumentMatchers.any()))
.thenThrow(new NoFallbackAvailableException("print unavailable",
new IllegalStateException("down")));
assertThatThrownBy(() -> service.generate(10001L, 70001L, null, null))
.isInstanceOf(BizException.class)
.hasMessage("打印服务暂不可用,请稍后重试")
.extracting(value -> ((BizException) value).getResultCode())
.isEqualTo(ResultCode.SERVICE_UNAVAILABLE);
}
@Test
void batchCombinesSuccessfulOrdersAndReportsIsolatedFailures() {
when(labels.requireInboundIdByOrder(10001L, 60001L)).thenReturn(70001L);
when(labels.requireInboundIdByOrder(10001L, 60002L))
.thenThrow(new BizException(ResultCode.NOT_FOUND, "missing"));
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("request-batch"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateByOrders(
10001L, List.of(60001L, 60002L), "request-batch");
assertThat(response.getHeaders().getFirst("X-Carton-Label-Order-Count")).isEqualTo("2");
assertThat(response.getHeaders().getFirst("X-Carton-Label-Failure-Count")).isEqualTo("1");
assertThat(response.getHeaders().getFirst("X-Carton-Label-Failures")).contains("60002:A0404");
verify(print).generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("request-batch"), org.mockito.ArgumentMatchers.any());
}
@Test
void platformBatchCombinesLabelsAcrossCustomers() {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
when(labels.list(10002L, 70002L, null)).thenReturn(batch());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
when(print.generateInboundMarks(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.eq("platform-batch"), org.mockito.ArgumentMatchers.any()))
.thenReturn(new ResponseEntity<>(new byte[] {1, 2, 3}, headers,
org.springframework.http.HttpStatus.OK));
ResponseEntity<byte[]> response = service.generateForPlatformInbounds(List.of(
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch");
assertThat(response.getHeaders().getFirst("X-Carton-Label-Order-Count")).isEqualTo("2");
assertThat(response.getBody()).containsExactly(1, 2, 3);
}
@Test
void platformBatchFailsWhenAnySelectedInboundCannotGenerateLabels() {
when(labels.list(10001L, 70001L, null)).thenReturn(batch());
when(labels.list(10002L, 70002L, null))
.thenThrow(new BizException(ResultCode.NOT_FOUND, "箱唛入库单不存在"));
assertThatThrownBy(() -> service.generateForPlatformInbounds(List.of(
new InboundCartonLabels.PlatformBatchItem(10001L, 70001L),
new InboundCartonLabels.PlatformBatchItem(10002L, 70002L)), "platform-batch"))
.isInstanceOf(BizException.class)
.hasMessage("箱唛入库单不存在");
verifyNoInteractions(print);
}
private InboundCartonLabels.Batch batch() {
InboundCartonLabels.Label label = new InboundCartonLabels.Label(
1L, 2L, 1, "音箱", 19, "PCHK2608040174#19-1", 19, 1,
"50x40x30cm", new BigDecimal("50"), new BigDecimal("40"), new BigDecimal("30"),
"PCHK2608040174", "MCC1", "CTNS:19-1", "洛杉矶普船海卡");
return new InboundCartonLabels.Batch(70001L, 80001L, "PCHK2608040174", 19, List.of(label));
}
private Pallet dropshipPallet(String status) {
SkuItem item = new SkuItem(70001L, "PCHK2608040174", 2L,
"SKU-1", "音箱", 1, 19, List.of("PCHK2608040174#19-1"));
return new Pallet(90001L, "DP-260911-0001", status, 1, 1, 0,
BigDecimal.ONE, BigDecimal.ONE, 1, List.of(item), List.of(), null);
}
private void stubPalletFacts(long palletId, int sequence, int total) {
DropshipPalletizationMapper.PrintInboundRow inbound =
new DropshipPalletizationMapper.PrintInboundRow();
inbound.setTenantId(10001L);
inbound.setInboundId(70001L);
inbound.setInboundCode("PCHK2608040174");
inbound.setDestinationWarehouse("纽约 N.JDF");
inbound.setCartonCount(palletId == 90001L ? 1 : 0);
inbound.setItemQuantity(palletId == 90001L ? 19 : 0);
when(palletization.printInboundRows(palletId)).thenReturn(List.of(inbound));
DropshipPalletizationMapper.PrintSkuRow sku = new DropshipPalletizationMapper.PrintSkuRow();
sku.setSkuCode("SKU-1");
sku.setCartonCount(1);
sku.setItemQuantity(19);
sku.setMinUnitsPerCarton(19);
sku.setMaxUnitsPerCarton(19);
when(palletization.printSkuRows(palletId))
.thenReturn(palletId == 90001L ? List.of(sku) : List.of());
when(palletization.palletPosition(palletId))
.thenReturn(new DropshipPalletizationMapper.PalletPosition(sequence, total));
when(customerSnapshots.customerNickname(10001L)).thenReturn("小虫科技");
}
}