test(firstleg): extend shipment demo regression

This commit is contained in:
2026-09-01 12:02:47 +08:00
parent 49d216cc13
commit 53078b4281
2 changed files with 103 additions and 0 deletions
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.customs.container;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.customs.container.dto.ContainerCustomsDtos.ActionRequest;
import com.baiyue.firstleg.customs.container.dto.ContainerCustomsDtos.ScopeRequest;
import com.baiyue.firstleg.customs.container.mapper.ContainerCustomsMapper;
import com.baiyue.firstleg.customs.container.service.ContainerCustomsService;
import com.baiyue.firstleg.customs.client.CustomsFileClient;
@@ -108,6 +109,35 @@ class ContainerCustomsServiceTest {
verify(mapper,never()).freezeScopes(anyLong());
}
@Test void declaringContainerFreezesUploadReplacementScopeAndDeletionWithoutDrift(){
var inbound=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","TAX_REFUND");
var declaring=container(ContainerCustomsService.DECLARING,3);
var source=document(1L);
var file=new MockMultipartFile("file","source.bin","application/octet-stream",new byte[]{1});
when(mapper.planInbounds(9L)).thenReturn(List.of(inbound));
when(mapper.container(9L)).thenReturn(declaring);
when(mapper.document(1L)).thenReturn(source);
when(mapper.lockContainerById(2L)).thenReturn(declaring);
assertThatThrownBy(()->service.uploadSource(7L,9L,5L,"upload-after-start",List.of(11L),file))
.isInstanceOf(BizException.class).hasMessageContaining("资料已锁定");
assertThatThrownBy(()->service.replaceSource(7L,1L,5L,"replace-after-start",new ScopeRequest(List.of(11L),1),file))
.isInstanceOf(BizException.class).hasMessageContaining("资料已锁定");
assertThatThrownBy(()->service.updateScope(7L,1L,5L,"scope-after-start",new ScopeRequest(List.of(11L),1)))
.isInstanceOf(BizException.class).hasMessageContaining("资料已锁定");
assertThatThrownBy(()->service.deleteSource(7L,1L,5L,"delete-after-start",1))
.isInstanceOf(BizException.class).hasMessageContaining("资料已锁定");
verifyNoInteractions(files);
verify(mapper,never()).insertDocument(anyLong(),anyLong(),anyLong(),anyLong(),anyString(),anyString(),anyString(),anyLong(),anyLong());
verify(mapper,never()).replaceDocument(anyLong(),anyLong(),anyInt(),anyLong(),anyString(),anyString(),anyString(),anyLong(),anyLong());
verify(mapper,never()).touchScope(anyLong(),anyLong(),anyInt());
verify(mapper,never()).deleteDocument(anyLong(),anyLong(),anyInt(),anyLong());
verify(mapper,never()).insertDocumentVersion(anyLong(),anyLong(),anyInt(),anyLong(),anyString(),anyString(),anyString(),anyLong(),anyString(),anyString(),anyLong());
assertThat(source.version()).isEqualTo(1);
assertThat(source.status()).isEqualTo("ACTIVE");
}
@Test void concurrentStartWithSameKeyReplaysOneAggregateAndDifferentPayloadConflicts() throws Exception {
var row=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","TAX_REFUND");
var ready=container(ContainerCustomsService.READY,2);
@@ -271,6 +301,27 @@ class ContainerCustomsServiceTest {
verify(mapper,never()).complete(anyLong(),anyInt(),anyLong(),anyString());
}
@Test void allBuyContainerCompletesOnceAndPublishesAutoDispatchTrigger(){
var declaring=container(ContainerCustomsService.DECLARING,3);
var completed=container(ContainerCustomsService.COMPLETED,4);
var buy=new ContainerCustomsMapper.InboundRow(9L,"LP9",8L,"COMPLETED","CAB","C",11L,21L,"O","I",7L,"客户","BUY_DECLARATION");
when(mapper.claimCommand(anyLong(),eq(5L),eq("COMPLETE_ALL_BUY_CUSTOMS_9"),eq("all-buy-key"),anyString())).thenReturn(1);
when(mapper.lockContainer(9L)).thenReturn(declaring);
when(mapper.planInbounds(9L)).thenReturn(List.of(buy));
when(mapper.complete(2L,3,5L,"admin")).thenReturn(1);
when(mapper.completeCommand(5L,"COMPLETE_ALL_BUY_CUSTOMS_9","all-buy-key",2L)).thenReturn(1);
when(mapper.container(9L)).thenReturn(completed);
when(mapper.documents(2L,null)).thenReturn(List.of());
var result=service.completeAllBuy(9L,5L,"admin","all-buy-key",new ActionRequest(3,true,"代理已完成"));
assertThat(result.status()).isEqualTo(ContainerCustomsService.COMPLETED);
verify(mapper,times(1)).complete(2L,3,5L,"admin");
verify(mapper,times(1)).insertHistory(anyLong(),eq(2L),eq("COMPLETE_CUSTOMS"),
eq(ContainerCustomsService.DECLARING),eq(ContainerCustomsService.COMPLETED),eq("代理已完成"),eq(5L),eq("admin"));
verify(events,times(1)).publishEvent(new ContainerCustomsService.CustomsCompletedEvent(8L));
}
private static ContainerCustomsMapper.DocumentRow document(long id){return new ContainerCustomsMapper.DocumentRow(id,2L,7L,10L,"source.bin","a".repeat(64),"application/octet-stream",1L,"ACTIVE",5L,java.time.LocalDateTime.now(),1,java.time.LocalDateTime.now(),1);}
private static ContainerCustomsMapper.ContainerRow container(String status,int version){return new ContainerCustomsMapper.ContainerRow(2L,9L,8L,status,null,null,null,null,null,null,null,null,version);}
private void assertMerchantOrderNotFound(long tenantId,long orderId){
@@ -67,6 +67,37 @@ class OceanBookingDocumentServiceTest {
verify(repository, times(1)).insert(any(OceanBookingDocumentEntity.class));
}
@Test void draftAcceptsBookingConfirmationAndBillOfLadingAsTwoVersionedFiles() {
MockMultipartFile confirmation = new MockMultipartFile("file", "confirmation.pdf",
"application/pdf", "confirmation".getBytes());
MockMultipartFile bill = new MockMultipartFile("file", "bill.pdf",
"application/pdf", "bill".getBytes());
when(repository.claimOperation(anyLong(), eq(7L), eq("UPLOAD_DOCUMENT"), anyString(), anyString()))
.thenReturn(true);
when(repository.findForUpdate(10L)).thenReturn(draft(0), draft(1));
when(files.upload("token", 7L, 9L, "OCEAN_BOOKING_DOCUMENT", "10", confirmation))
.thenReturn(ApiResponse.success(metadata(91L)));
when(files.upload("token", 7L, 9L, "OCEAN_BOOKING_DOCUMENT", "10", bill))
.thenReturn(ApiResponse.success(metadata(92L)));
when(repository.touchDraft(eq(10L), anyInt(), eq(7L), eq("admin"))).thenReturn(1);
when(repository.completeOperation(eq(10L), eq(7L), eq("UPLOAD_DOCUMENT"), anyString(), anyString()))
.thenReturn(1);
var first = service.upload(10L, 0, "BOOKING_CONFIRMATION", "confirmation-key",
confirmation, 7L, "admin");
var second = service.upload(10L, 1, "BILL_OF_LADING", "bill-key", bill, 7L, "admin");
assertThat(first.bookingVersion()).isEqualTo(1);
assertThat(second.bookingVersion()).isEqualTo(2);
ArgumentCaptor<OceanBookingDocumentEntity> documents =
ArgumentCaptor.forClass(OceanBookingDocumentEntity.class);
verify(repository, times(2)).insert(documents.capture());
assertThat(documents.getAllValues()).extracting(OceanBookingDocumentEntity::getDocumentType)
.containsExactly("BOOKING_CONFIRMATION", "BILL_OF_LADING");
assertThat(documents.getAllValues()).extracting(OceanBookingDocumentEntity::getFileId)
.containsExactly(91L, 92L);
}
@Test void attachRejectsCrossScopeFileBeforePersisting() {
when(repository.claimOperation(any(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
when(repository.findForUpdate(10L)).thenReturn(draft(1));
@@ -121,6 +152,27 @@ class OceanBookingDocumentServiceTest {
verify(files).metadata("token", 9L, "OCEAN_BOOKING_DOCUMENT", "10", 91L);
}
@Test void releaseGateRejectsWhenBothActiveFileReferencesNoLongerExist() {
OceanBookingEntity booking = draft(3);
OceanBookingDocumentEntity confirmation = activeDocument(20L, 91L, "BOOKING_CONFIRMATION");
OceanBookingDocumentEntity bill = activeDocument(21L, 92L, "BILL_OF_LADING");
when(repository.activeDocuments(10L)).thenReturn(List.of(confirmation, bill));
assertThatThrownBy(() -> service.requireActiveValidDocument(booking))
.isInstanceOf(BizException.class).hasMessageContaining("有效订舱确认或提单文件");
verify(files).metadata("token", 9L, "OCEAN_BOOKING_DOCUMENT", "10", 91L);
verify(files).metadata("token", 9L, "OCEAN_BOOKING_DOCUMENT", "10", 92L);
verify(repository, never()).touchDraft(anyLong(), anyInt(), anyLong(), anyString());
}
private static OceanBookingDocumentEntity activeDocument(long id, long fileId, String type) {
OceanBookingDocumentEntity document = new OceanBookingDocumentEntity();
document.setId(id); document.setOceanBookingId(10L); document.setFileId(fileId);
document.setDocumentType(type); document.setValid(true);
return document;
}
private static OceanBookingEntity draft(int version) {
OceanBookingEntity booking = new OceanBookingEntity();
booking.setId(10L); booking.setCustomerId(9L); booking.setStatus("DRAFT"); booking.setVersion(version);