优化
This commit is contained in:
+3
-1
@@ -25,6 +25,7 @@ class ContainerControllerContractTest {
|
||||
assertPermission("updateTransport", "platform:firstleg:container:edit");
|
||||
assertPermission("uploadClearance", "platform:firstleg:customs:manage");
|
||||
assertPermission("overview", "platform:firstleg:container:list");
|
||||
assertPermission("downloadPdaPhoto", "platform:firstleg:container:list");
|
||||
assertPermission("markException", "platform:firstleg:container:edit");
|
||||
assertPermission("cancelException", "platform:firstleg:container:edit");
|
||||
assertPermission("logisticsDocument", "platform:firstleg:container:list");
|
||||
@@ -38,7 +39,8 @@ class ContainerControllerContractTest {
|
||||
assertThat(openapi).contains("/api/platform/v1/containers/{id}/overview:",
|
||||
"/documents/{documentType}/{fileId}/download:", "ExceptionRequest",
|
||||
"enum: [DELIVERY_ORDER, PICKUP_ORDER]", "Idempotency-Key", "photoCounts:",
|
||||
"downloadPath:", "version: {type: integer, minimum: 0}")
|
||||
"/pda/photos/{fileId}/download:", "downloadPath:", "workflowStatus:",
|
||||
"AWAITING_REVIEW", "version: {type: integer, minimum: 0}")
|
||||
.doesNotContain("?containerNo", "containerNo in: query");
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class FbaPricingContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("契约声明二期边界计费、已发布费用调整和幂等头")
|
||||
@DisplayName("契约声明二期边界计费、人工费用调整和幂等头")
|
||||
void documentsPricingLifecycle() throws Exception {
|
||||
String openapi = new ClassPathResource("openapi/fba-pricing-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
@@ -53,6 +53,10 @@ class FbaPricingContractTest {
|
||||
.contains("不再以 carrierId、业务 customerId 或客户计费规则作为前置")
|
||||
.contains("该动作涉及后续仓储流程,本轮二期验收不调用")
|
||||
.contains("不回退 WAIT_SPLIT 状态")
|
||||
.contains("expectedFeeVersion")
|
||||
.contains("chargeableQuantity")
|
||||
.contains("unitPrice")
|
||||
.doesNotContain("required: [subtotalAmount, additionalAmount, reason]")
|
||||
.doesNotContain("应收")
|
||||
.doesNotContain("应付")
|
||||
.doesNotContain("待客户确认");
|
||||
|
||||
+17
-28
@@ -1,10 +1,10 @@
|
||||
package com.baiyue.firstleg.controller;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
|
||||
import com.baiyue.firstleg.dto.MeasurementApprovalResponse;
|
||||
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
|
||||
import com.baiyue.firstleg.service.AutomaticInboundCompletionService;
|
||||
import com.baiyue.firstleg.service.InitialInboundPricingService;
|
||||
import com.baiyue.firstleg.service.MeasurementReviewService;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -46,50 +46,39 @@ class InboundMeasurementReviewContractTest {
|
||||
.contains("/api/platform/v1/inbounds/{inboundId}/measurements/reject:")
|
||||
.contains("firstleg:inbound:exception-release")
|
||||
.contains("Idempotency-Key")
|
||||
.contains("审核事务提交后服务端自动执行计费")
|
||||
.contains("接口不会伪造 WAIT_SPLIT")
|
||||
.contains("成功 data 为 FeeSnapshotResponse")
|
||||
.contains("nextStatus=WAIT_SPLIT")
|
||||
.contains("初算异常只返回非阻断提示")
|
||||
.contains("data.processStatus 固定为 WAIT_SPLIT")
|
||||
.contains("feeStatus=UNPRICED 且主流程仍成功")
|
||||
.contains("name: inboundId")
|
||||
.contains("schema: {type: string, pattern:")
|
||||
.doesNotContain("待客户确认");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("审核事务完成后才启动自动编排并返回真实费用快照")
|
||||
void shouldCompleteApprovedReviewAfterReviewTransactionReturns() {
|
||||
@DisplayName("审核先提交待分柜,再执行不阻塞的初算")
|
||||
void shouldApproveBeforeBestEffortInitialPricing() {
|
||||
MeasurementReviewService review = mock(MeasurementReviewService.class);
|
||||
AutomaticInboundCompletionService completion = mock(AutomaticInboundCompletionService.class);
|
||||
InitialInboundPricingService initialPricing = mock(InitialInboundPricingService.class);
|
||||
PlatformInboundMeasurementReviewController controller = new PlatformInboundMeasurementReviewController();
|
||||
ReflectionTestUtils.setField(controller, "service", review);
|
||||
ReflectionTestUtils.setField(controller, "automaticCompletion", completion);
|
||||
ReflectionTestUtils.setField(controller, "initialPricing", initialPricing);
|
||||
AuthenticatedUser user = new AuthenticatedUser(9L, 0L, 0L, null,
|
||||
"客服", "PLATFORM", "WEB", Set.of("firstleg:inbound:measurement-review"));
|
||||
MeasurementReviewRequest request = new MeasurementReviewRequest(30L, 4, "通过");
|
||||
var fee = mock(com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse.class);
|
||||
when(completion.completeApproved(10001L, 88L, 9L, "客服", 30L, 5)).thenReturn(fee);
|
||||
var result = new MeasurementApprovalResponse(88L, 30L, "WAIT_SPLIT", "UNPRICED",
|
||||
false, "待人工维护费用", null);
|
||||
when(initialPricing.tryCalculate(10001L, 9L, 88L, 30L, 5)).thenReturn(result);
|
||||
|
||||
var first = controller.approve(user, 10001L, 88L, "approve-1", request);
|
||||
var replay = controller.approve(user, 10001L, 88L, "approve-1", request);
|
||||
|
||||
assertThat(first.data()).isSameAs(fee);
|
||||
assertThat(replay.data()).isSameAs(fee);
|
||||
var ordered = inOrder(review, completion);
|
||||
assertThat(first.data()).isSameAs(result);
|
||||
assertThat(replay.data()).isSameAs(result);
|
||||
var ordered = inOrder(review, initialPricing);
|
||||
ordered.verify(review).approve(10001L, 9L, "客服", 88L, "approve-1", request);
|
||||
ordered.verify(completion).completeApproved(10001L, 88L, 9L, "客服", 30L, 5);
|
||||
ordered.verify(initialPricing).tryCalculate(10001L, 9L, 88L, 30L, 5);
|
||||
ordered.verify(review).approve(10001L, 9L, "客服", 88L, "approve-1", request);
|
||||
ordered.verify(completion).completeApproved(10001L, 88L, 9L, "客服", 30L, 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAuditAutomaticInboundCompletion() throws Exception {
|
||||
Method method = AutomaticInboundCompletionService.class.getDeclaredMethod("completeApproved",
|
||||
long.class, long.class, long.class, String.class, long.class, int.class);
|
||||
BusinessAudit audit = method.getAnnotation(BusinessAudit.class);
|
||||
assertThat(audit.event()).isEqualTo("INBOUND_AUTO_COMPLETE");
|
||||
assertThat(audit.resourceType()).isEqualTo("INBOUND");
|
||||
assertThat(audit.resourceId()).isEqualTo("#inboundId");
|
||||
assertThat(audit.captureBefore()).isFalse();
|
||||
ordered.verify(initialPricing).tryCalculate(10001L, 9L, 88L, 30L, 5);
|
||||
}
|
||||
|
||||
private void assertAuthority(String name, String expression, Class<?>... types) throws Exception {
|
||||
|
||||
@@ -23,6 +23,12 @@ class PriceBookContractTest {
|
||||
@DisplayName("固定十五个渠道且海派使用区域重量模型")
|
||||
void keepsStableChannelCatalog() {
|
||||
assertThat(PriceBookChannelCatalog.CHANNELS).hasSize(15);
|
||||
assertThat(PriceBookChannelCatalog.CHANNELS)
|
||||
.extracting(value -> value.channelCode() + ":" + value.inboundCodePrefix())
|
||||
.contains("MATSON_CLX_TRUCK:ZBYHK", "OA_LA_FULL_TRUCK:PCKK", "OA_CHICAGO_RAIL:CPHT");
|
||||
assertThat(PriceBookChannelCatalog.CHANNELS)
|
||||
.filteredOn(value -> "OA_NEW_YORK_TRUCK".equals(value.channelCode()))
|
||||
.singleElement().extracting(value -> value.overseasInboundCodePrefix()).isEqualTo("NPHWK");
|
||||
assertThat(PriceBookChannelCatalog.CHANNELS.stream()
|
||||
.map(PriceBookChannelCatalog.ChannelDefinition::channelCode))
|
||||
.containsExactly(
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ class TruckAppointmentControllerContractTest {
|
||||
@Test void exposesDedicatedEndpointAndPermissions() {String p=PlatformTruckAppointmentController.class.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class).value()[0];assertThat(p).isEqualTo("/api/platform/v1/truck-appointments"); for(String n:new String[]{"page","detail","history","attachments","attach","upload","deleteAttachment","create","edit","action","reschedule"}) assertThat(java.util.Arrays.stream(PlatformTruckAppointmentController.class.getDeclaredMethods()).anyMatch(m->m.getName().equals(n))).isTrue();}
|
||||
@Test void schemaHasAggregateTablesStatesAndActualEventTimes() throws Exception {String s=new ClassPathResource("db/phase3-truck-appointment-schema.sql").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("fl_truck_appointment","fl_truck_appointment_history","fl_truck_appointment_attachment","fl_truck_appointment_operation","PENDING_DISPATCH","PICKUP_IN_PROGRESS","ARRIVED_WAREHOUSE","RESCHEDULED","actual_arrived_warehouse_at","actual_loading_at","actual_departed_at","CALL phase3_truck_add_column");}
|
||||
@Test void openApiDocumentsLifecycleIdempotencyAndActualTimes() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("/api/platform/v1/truck-appointments/{id}/{action}:","/api/platform/v1/truck-appointments/{id}/reschedule:","name: Idempotency-Key","required: true","actual event timestamps");}
|
||||
@Test void openApiFreezesAttachmentRequestAndOwnershipContract() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("AttachmentRequest:","required: [fileId]","maxLength: 64","TRUCK_APPOINTMENT_ATTACHMENT","Deletes the appointment reference only","DetailStableReferences:","oceanBookingNo:","shippingCompanyName:","bookingAgentName:","cabinetNo:");}
|
||||
@Test void methodsHavePlatformPermissionsAndAuthenticatedScope() {
|
||||
Map<String,String> expected=Map.ofEntries(
|
||||
Map.entry("page","platform:firstleg:truck-appointment:list"), Map.entry("detail","platform:firstleg:truck-appointment:list"),
|
||||
|
||||
+4
-3
@@ -11,10 +11,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InboundOrderStatusSqlContractTest {
|
||||
@Test
|
||||
void advancesThroughPricingBeforeOpeningWaitSplit() {
|
||||
void opensWaitSplitImmediatelyAfterMeasurementApproval() {
|
||||
assertThat(sql("approveMeasurement"))
|
||||
.contains("process_status = 'WAIT_PRICING'")
|
||||
.doesNotContain("inbounded_at =");
|
||||
.contains("process_status = 'WAIT_SPLIT'")
|
||||
.contains("inbounded_at = CURRENT_TIMESTAMP(3)")
|
||||
.doesNotContain("process_status = 'WAIT_PRICING'");
|
||||
assertThat(sql("completeInbound"))
|
||||
.contains("process_status = 'WAIT_SPLIT'")
|
||||
.contains("inbounded_at = CURRENT_TIMESTAMP(3)")
|
||||
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(AutomaticInboundCompletionService.class)
|
||||
class AutomaticInboundCompletionServiceTest {
|
||||
@MockBean private FbaPricingService pricing;
|
||||
@MockBean private InboundWorkflowRepository workflow;
|
||||
@MockBean private InboundCompletionOrchestrator completion;
|
||||
@Resource private AutomaticInboundCompletionService service;
|
||||
|
||||
@Test
|
||||
void shouldHandleCase01() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(
|
||||
order("WAIT_PRICING", "UNPRICED", 4),
|
||||
order("FEE_CONFIRMING", "CALCULATED", 5),
|
||||
order("FEE_CONFIRMING", "CONFIRMED", 6),
|
||||
order("INBOUNDING", "CONFIRMED", 7),
|
||||
order("WAIT_SPLIT", "SENT", 8));
|
||||
|
||||
FeeSnapshotResponse result = service.completeApproved(10001L, 88L, 9L, "客服", 30L, 4);
|
||||
|
||||
assertThat(result).isSameAs(fee);
|
||||
verify(pricing).calculate(10001L, 9L, 88L, null, "auto-88-30-4-calculate");
|
||||
verify(pricing).confirm(10001L, 9L, 88L, "auto-88-30-4-confirm");
|
||||
verify(completion).beginSendFeeAndInbound(10001L, 9L, "客服", 88L, 501L, 6,
|
||||
"auto-88-30-4-begin");
|
||||
verify(completion).completeSendFeeAndInbound(10001L, 9L, "客服", 88L, 501L, 7,
|
||||
"auto-88-30-4-complete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCase02() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(order("WAIT_SPLIT", "SENT", 8));
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
|
||||
assertThat(service.completeApproved(10001L, 88L, 9L, "客服", 30L, 4)).isSameAs(fee);
|
||||
|
||||
verify(pricing, never()).calculate(anyLong(), anyLong(), anyLong(),
|
||||
org.mockito.ArgumentMatchers.any(), anyString());
|
||||
verify(pricing, never()).confirm(anyLong(), anyLong(), anyLong(), anyString());
|
||||
verify(completion, never()).beginSendFeeAndInbound(anyLong(), anyLong(), anyString(),
|
||||
anyLong(), anyLong(), org.mockito.ArgumentMatchers.anyInt(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHandleCase03() {
|
||||
FeeSnapshotResponse fee = fee(501L);
|
||||
when(pricing.latest(10001L, 88L)).thenReturn(fee);
|
||||
when(workflow.findOwned(10001L, 88L)).thenReturn(
|
||||
order("INBOUNDING", "CONFIRMED", 7), order("WAIT_SPLIT", "SENT", 8));
|
||||
|
||||
service.completeApproved(10001L, 88L, 9L, "客服", 30L, 4);
|
||||
|
||||
verify(completion).completeSendFeeAndInbound(10001L, 9L, "客服", 88L, 501L, 7,
|
||||
"auto-88-30-4-complete");
|
||||
verify(pricing, never()).calculate(anyLong(), anyLong(), anyLong(),
|
||||
org.mockito.ArgumentMatchers.any(), anyString());
|
||||
verify(pricing, never()).confirm(anyLong(), anyLong(), anyLong(), anyString());
|
||||
}
|
||||
|
||||
private static InboundOrderEntity order(String process, String feeStatus, int version) {
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus(process);
|
||||
order.setFeeStatus(feeStatus);
|
||||
order.setVersion(version);
|
||||
return order;
|
||||
}
|
||||
|
||||
private static FeeSnapshotResponse fee(long feeId) {
|
||||
FeeSnapshotResponse fee = mock(FeeSnapshotResponse.class);
|
||||
when(fee.feeId()).thenReturn(feeId);
|
||||
return fee;
|
||||
}
|
||||
}
|
||||
+64
-1
@@ -5,6 +5,10 @@ import com.baiyue.firstleg.dto.ContainerOverviewDtos.ExceptionRequest;
|
||||
import com.baiyue.firstleg.entity.ContainerBookingEntity;
|
||||
import com.baiyue.firstleg.mapper.ContainerOverviewMapper;
|
||||
import com.baiyue.firstleg.repository.ContainerRepository;
|
||||
import com.baiyue.firstleg.client.LoadingEvidenceFileClient;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -13,6 +17,7 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -20,9 +25,67 @@ import static org.mockito.Mockito.*;
|
||||
class ContainerOverviewServiceTest {
|
||||
@Mock ContainerRepository containers;
|
||||
@Mock ContainerOverviewMapper mapper;
|
||||
@Mock LoadingEvidenceFileClient evidenceFiles;
|
||||
@InjectMocks ContainerOverviewService service;
|
||||
|
||||
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
|
||||
@BeforeEach void setUp() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
ReflectionTestUtils.setField(service, "internalToken", "internal-token");
|
||||
}
|
||||
|
||||
@Test void overviewExposesAwaitingReviewWorkflowAndFirstlegPhotoDownloadPath() {
|
||||
ContainerBookingEntity booking = new ContainerBookingEntity();
|
||||
booking.setId(41L); booking.setVersion(3); booking.setPlanId(52L);
|
||||
when(containers.find(41L)).thenReturn(booking);
|
||||
when(mapper.pickTask(41L)).thenReturn(new ContainerOverviewMapper.PickTaskRow(
|
||||
61L, 52L, 41L, "LOADING", 7));
|
||||
when(mapper.itemCount(61L)).thenReturn(2);
|
||||
when(mapper.completedItemCount(61L)).thenReturn(2);
|
||||
when(mapper.unresolvedItemCount(61L)).thenReturn(0);
|
||||
when(mapper.photos(52L)).thenReturn(java.util.List.of(new ContainerOverviewMapper.PhotoRow(
|
||||
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "door.jpg", "ACTIVE", LocalDateTime.now())));
|
||||
when(mapper.documents(41L)).thenReturn(java.util.List.of());
|
||||
when(mapper.exceptions(41L)).thenReturn(java.util.List.of());
|
||||
when(mapper.transport(41L)).thenReturn(java.util.List.of());
|
||||
when(mapper.pickTimeline(61L)).thenReturn(java.util.List.of());
|
||||
when(mapper.pickExceptionReasons(61L)).thenReturn(java.util.List.of());
|
||||
|
||||
var overview = service.overview(41L);
|
||||
|
||||
assertThat(overview.pda().taskStatus()).isEqualTo("LOADING");
|
||||
assertThat(overview.pda().workflowStatus()).isEqualTo("AWAITING_REVIEW");
|
||||
assertThat(overview.pda().photos().get(0).downloadPath())
|
||||
.isEqualTo("/api/platform/v1/containers/41/pda/photos/101/download");
|
||||
}
|
||||
|
||||
@Test void downloadsOnlyActivePhotoOwnedByContainerLoadingPlan() {
|
||||
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L);
|
||||
when(containers.find(41L)).thenReturn(booking);
|
||||
when(mapper.pickTask(41L)).thenReturn(new ContainerOverviewMapper.PickTaskRow(
|
||||
61L, 52L, 41L, "LOADING", 7));
|
||||
when(mapper.photos(52L)).thenReturn(java.util.List.of(new ContainerOverviewMapper.PhotoRow(
|
||||
71L, 81L, 91L, "LOADING_DOOR_OPEN", 101L, "door.jpg", "ACTIVE", LocalDateTime.now())));
|
||||
when(evidenceFiles.download("internal-token", 81L, 91L, 101L)).thenReturn(ResponseEntity.ok()
|
||||
.contentType(MediaType.IMAGE_JPEG).body(new byte[]{1, 2, 3}));
|
||||
|
||||
var download = service.downloadPhoto(41L, 101L);
|
||||
|
||||
assertThat(download.fileName()).isEqualTo("door.jpg");
|
||||
assertThat(download.contentType()).isEqualTo(MediaType.IMAGE_JPEG);
|
||||
assertThat(download.content()).containsExactly(1, 2, 3);
|
||||
}
|
||||
|
||||
@Test void rejectsPhotoFromAnotherContainerBeforeCallingFileService() {
|
||||
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L);
|
||||
when(containers.find(41L)).thenReturn(booking);
|
||||
when(mapper.pickTask(41L)).thenReturn(new ContainerOverviewMapper.PickTaskRow(
|
||||
61L, 52L, 41L, "LOADING", 7));
|
||||
when(mapper.photos(52L)).thenReturn(java.util.List.of());
|
||||
|
||||
assertThatThrownBy(() -> service.downloadPhoto(41L, 999L))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("不属于该货柜");
|
||||
verifyNoInteractions(evidenceFiles);
|
||||
}
|
||||
|
||||
@Test void markLocksContainerAndPersistsIdempotentImmutableHistory() {
|
||||
ContainerBookingEntity booking = new ContainerBookingEntity(); booking.setId(41L); booking.setVersion(3);
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@@ -302,7 +303,9 @@ class FbaPricingServiceTest {
|
||||
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(901L); return null; })
|
||||
.when(repository).insertIdempotency(any());
|
||||
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
|
||||
when(repository.supersedeSent(TENANT, 501L)).thenReturn(1);
|
||||
when(repository.supersedeCurrent(TENANT, 501L, "SENT")).thenReturn(1);
|
||||
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("SENT"), eq("KG"), any()))
|
||||
.thenReturn(1);
|
||||
doAnswer(invocation -> {
|
||||
InboundFeeEntity fee = invocation.getArgument(0);
|
||||
fee.setId(502L);
|
||||
@@ -314,18 +317,80 @@ class FbaPricingServiceTest {
|
||||
|
||||
FeeSnapshotResponse response = service.adjustPublished(TENANT, OPERATOR, INBOUND,
|
||||
new com.baiyue.firstleg.dto.FbaPricingDtos.AdjustFeeRequest(
|
||||
new BigDecimal("100.00"), new BigDecimal("20.00"), "客服修正"), "adjust-1");
|
||||
1, new BigDecimal("12.5"), new BigDecimal("8.00"),
|
||||
new BigDecimal("20.00"), "客服修正"), "adjust-1");
|
||||
|
||||
assertThat(response.feeVersion()).isEqualTo(2);
|
||||
assertThat(response.feeStatus()).isEqualTo("SENT");
|
||||
assertThat(response.totalAmount()).isEqualByComparingTo("120.00");
|
||||
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||
assertThat(inbound.getProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||
verify(repository).supersedeSent(TENANT, 501L);
|
||||
assertThat(inserted.get().getChargeableQuantity()).isEqualByComparingTo("12.5000");
|
||||
verify(repository).supersedeCurrent(TENANT, 501L, "SENT");
|
||||
verify(financeOutbox).publishInbound(eq(FinanceFeeSourceOutboxService.ADJUSTED),
|
||||
eq(TENANT), eq(INBOUND), eq(502L), eq(2), eq(NOW));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("初算费用调整后保持初算状态且不发布财务来源")
|
||||
void adjustsCalculatedFeeWithoutPublishingIt() {
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
inbound.setProcessStatus("WAIT_SPLIT");
|
||||
inbound.setFeeStatus("CALCULATED");
|
||||
InboundFeeEntity current = savedFee("CALCULATED");
|
||||
AtomicReference<InboundFeeEntity> inserted = new AtomicReference<>();
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(current);
|
||||
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_ADJUST", "adjust-calculated")).thenReturn(null);
|
||||
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(902L); return null; })
|
||||
.when(repository).insertIdempotency(any());
|
||||
when(repository.nextFeeVersion(TENANT, INBOUND)).thenReturn(2);
|
||||
when(repository.supersedeCurrent(TENANT, 501L, "CALCULATED")).thenReturn(1);
|
||||
when(repository.markInboundAdjusted(eq(TENANT), eq(INBOUND), eq("CALCULATED"), eq("KG"), any()))
|
||||
.thenReturn(1);
|
||||
doAnswer(invocation -> {
|
||||
InboundFeeEntity fee = invocation.getArgument(0);
|
||||
fee.setId(503L);
|
||||
inserted.set(fee);
|
||||
return null;
|
||||
}).when(repository).insertFee(any());
|
||||
when(repository.completeIdempotency(any(), eq(503L), eq(NOW))).thenReturn(1);
|
||||
when(repository.findFee(TENANT, 503L)).thenAnswer(invocation -> inserted.get());
|
||||
|
||||
FeeSnapshotResponse response = service.adjustPublished(TENANT, OPERATOR, INBOUND,
|
||||
new com.baiyue.firstleg.dto.FbaPricingDtos.AdjustFeeRequest(
|
||||
1, new BigDecimal("12.5"), new BigDecimal("8.00"),
|
||||
new BigDecimal("20.00"), "客服修正"), "adjust-calculated");
|
||||
|
||||
assertThat(response.feeStatus()).isEqualTo("CALCULATED");
|
||||
assertThat(response.totalAmount()).isEqualByComparingTo("120.00");
|
||||
assertThat(response.nextProcessStatus()).isEqualTo("WAIT_SPLIT");
|
||||
verify(repository).supersedeCurrent(TENANT, 501L, "CALCULATED");
|
||||
verify(financeOutbox, never()).publishInbound(any(), anyLong(), anyLong(), anyLong(), anyInt(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("费用版本已变化时拒绝覆盖当前版本")
|
||||
void rejectsAdjustmentWithStaleFeeVersion() {
|
||||
InboundPricingEntity inbound = inbound("10", "1");
|
||||
inbound.setProcessStatus("WAIT_SPLIT");
|
||||
InboundFeeEntity current = savedFee("CALCULATED");
|
||||
current.setFeeVersion(2);
|
||||
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
|
||||
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(current);
|
||||
|
||||
assertThatThrownBy(() -> service.adjustPublished(TENANT, OPERATOR, INBOUND,
|
||||
new com.baiyue.firstleg.dto.FbaPricingDtos.AdjustFeeRequest(
|
||||
1, new BigDecimal("12.5"), new BigDecimal("8.00"),
|
||||
BigDecimal.ZERO, "客服修正"), "stale-adjust"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.CONFLICT);
|
||||
|
||||
verify(repository, never()).insertIdempotency(any());
|
||||
verify(repository, never()).supersedeCurrent(anyLong(), anyLong(), any());
|
||||
}
|
||||
|
||||
private void readyRepository() {
|
||||
when(repository.findIdempotency(anyLong(), anyLong(), any(), any())).thenReturn(null);
|
||||
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(900L); return null; })
|
||||
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.entity.InboundFeeEntity;
|
||||
import com.baiyue.firstleg.entity.InboundOrderEntity;
|
||||
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("发送费用并入库状态编排")
|
||||
@SpringJUnitConfig(InboundCompletionOrchestrator.class)
|
||||
class InboundCompletionOrchestratorTest {
|
||||
@MockBean private FinanceFeeSourceOutboxService financeOutbox;
|
||||
@MockBean
|
||||
private InboundWorkflowRepository repository;
|
||||
@MockBean
|
||||
private InboundIdempotencyService idempotency;
|
||||
@MockBean
|
||||
private InboundFeeDetailService feeDetails;
|
||||
@Resource
|
||||
private InboundCompletionOrchestrator service;
|
||||
@Test
|
||||
@DisplayName("审核通过且费用和库位引用已准备时进入INBOUNDING")
|
||||
void shouldBeginInboundAfterAllGuardsPass() {
|
||||
Fixture fixture = fixture("APPROVED", false);
|
||||
when(fixture.repository.beginInbound(10001L, 10L, 9L, 4)).thenReturn(1);
|
||||
|
||||
fixture.service.beginSendFeeAndInbound(10001L, 9L, "操作员", 10L, 30L, 4, "send-1");
|
||||
|
||||
verify(fixture.repository).beginInbound(10001L, 10L, 9L, 4);
|
||||
verify(fixture.repository).insertHistory(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("测量驳回且无异常放行记录时禁止发送费用")
|
||||
void shouldBlockRejectedMeasurementWithoutRelease() {
|
||||
Fixture fixture = fixture("REJECTED", true);
|
||||
when(fixture.repository.hasExceptionRelease(10001L, 10L)).thenReturn(false);
|
||||
|
||||
assertThatThrownBy(() -> fixture.service.beginSendFeeAndInbound(
|
||||
10001L, 9L, "操作员", 10L, 30L, 4, "send-1"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.CONFLICT);
|
||||
verify(fixture.repository, never()).beginInbound(anyLong(), anyLong(), anyLong(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("异常放行标记必须与费用快照一致")
|
||||
void shouldRequireFeeSnapshotReleaseFlag() {
|
||||
Fixture fixture = fixture("REJECTED", false);
|
||||
when(fixture.repository.hasExceptionRelease(10001L, 10L)).thenReturn(true);
|
||||
|
||||
assertThatThrownBy(() -> fixture.service.beginSendFeeAndInbound(
|
||||
10001L, 9L, "操作员", 10L, 30L, 4, "send-1"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("异常放行标记");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("完成阶段原子发送费用、完成入库并写待投递事件")
|
||||
void shouldCompleteInboundAndWriteOutbox() {
|
||||
Fixture fixture = fixture("APPROVED", false);
|
||||
fixture.order.setProcessStatus("INBOUNDING");
|
||||
when(fixture.repository.completeInbound(10001L, 10L, 9L, 5)).thenReturn(1);
|
||||
when(fixture.repository.markFeeSent(10001L, 10L, 30L, 9L)).thenReturn(1);
|
||||
com.baiyue.firstleg.entity.InboundFeeDetailEntity published =
|
||||
new com.baiyue.firstleg.entity.InboundFeeDetailEntity();
|
||||
published.setSourceFeeVersion(1);
|
||||
when(feeDetails.publishFreight(eq(10001L), eq(10L), eq(30L), eq(9L), eq("操作员"),
|
||||
org.mockito.ArgumentMatchers.any(java.time.LocalDateTime.class))).thenReturn(published);
|
||||
|
||||
fixture.service.completeSendFeeAndInbound(
|
||||
10001L, 9L, "操作员", 10L, 30L, 5, "complete-1");
|
||||
|
||||
verify(fixture.repository).completeInbound(10001L, 10L, 9L, 5);
|
||||
verify(fixture.repository).markFeeSent(10001L, 10L, 30L, 9L);
|
||||
verify(feeDetails).publishFreight(eq(10001L), eq(10L), eq(30L), eq(9L), eq("操作员"),
|
||||
org.mockito.ArgumentMatchers.any(java.time.LocalDateTime.class));
|
||||
verify(financeOutbox).publishInbound(eq(FinanceFeeSourceOutboxService.PUBLISHED),
|
||||
eq(10001L), eq(10L), eq(30L), eq(1),
|
||||
org.mockito.ArgumentMatchers.any(java.time.LocalDateTime.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("条件更新失败时报告并发冲突且不发送费用")
|
||||
void shouldRejectConcurrentCompletion() {
|
||||
Fixture fixture = fixture("APPROVED", false);
|
||||
fixture.order.setProcessStatus("INBOUNDING");
|
||||
when(fixture.repository.completeInbound(10001L, 10L, 9L, 5)).thenReturn(0);
|
||||
|
||||
assertThatThrownBy(() -> fixture.service.completeSendFeeAndInbound(
|
||||
10001L, 9L, "操作员", 10L, 30L, 5, "complete-1"))
|
||||
.isInstanceOf(BizException.class)
|
||||
.extracting(error -> ((BizException) error).getResultCode())
|
||||
.isEqualTo(ResultCode.CONFLICT);
|
||||
verify(fixture.repository, never()).markFeeSent(anyLong(), anyLong(), anyLong(), anyLong());
|
||||
verify(financeOutbox, never()).publishInbound(any(), anyLong(), anyLong(), anyLong(),
|
||||
org.mockito.ArgumentMatchers.anyInt(),
|
||||
org.mockito.ArgumentMatchers.any(java.time.LocalDateTime.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("只有仓库预占已补偿后才通过显式方法回退")
|
||||
void shouldRollbackOnlyInboundingState() {
|
||||
Fixture fixture = fixture("APPROVED", false);
|
||||
when(fixture.repository.rollbackInbound(10001L, 10L, 9L, 5)).thenReturn(1);
|
||||
|
||||
fixture.service.rollbackSendFeeAndInbound(
|
||||
10001L, 9L, "操作员", 10L, 5, "仓库确认失败且已释放预占", "rollback-1");
|
||||
|
||||
verify(fixture.repository).rollbackInbound(10001L, 10L, 9L, 5);
|
||||
verify(fixture.repository).insertHistory(any());
|
||||
}
|
||||
|
||||
private Fixture fixture(String reviewStatus, boolean feeRelease) {
|
||||
when(idempotency.begin(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyString()))
|
||||
.thenReturn(new InboundIdempotencyService.Context(10001L, 1L, false));
|
||||
InboundOrderEntity order = new InboundOrderEntity();
|
||||
order.setProcessStatus("FEE_CONFIRMING");
|
||||
order.setFeeStatus("CONFIRMED");
|
||||
order.setMeasureReviewStatus(reviewStatus);
|
||||
InboundFeeEntity fee = new InboundFeeEntity();
|
||||
fee.setExceptionRelease(feeRelease);
|
||||
when(repository.findOwned(10001L, 10L)).thenReturn(order);
|
||||
when(repository.findConfirmedFee(10001L, 10L, 30L)).thenReturn(fee);
|
||||
when(repository.countPreparedAllocations(10001L, 10L)).thenReturn(1L);
|
||||
return new Fixture(service, repository, order);
|
||||
}
|
||||
|
||||
private record Fixture(InboundCompletionOrchestrator service,
|
||||
InboundWorkflowRepository repository,
|
||||
InboundOrderEntity order) {
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.dto.FbaPricingDtos.FeeSnapshotResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class InitialInboundPricingServiceTest {
|
||||
private final FbaPricingService pricing = mock(FbaPricingService.class);
|
||||
private final InitialInboundPricingService service = new InitialInboundPricingService();
|
||||
|
||||
InitialInboundPricingServiceTest() {
|
||||
ReflectionTestUtils.setField(service, "pricing", pricing);
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsCalculatedDraftWithoutChangingWaitSplit() {
|
||||
FeeSnapshotResponse fee = mock(FeeSnapshotResponse.class);
|
||||
when(fee.feeStatus()).thenReturn("CALCULATED");
|
||||
when(pricing.calculate(10001L, 9L, 88L, null, "initial-88-30-5")).thenReturn(fee);
|
||||
|
||||
var result = service.tryCalculate(10001L, 9L, 88L, 30L, 5);
|
||||
|
||||
assertThat(result.processStatus()).isEqualTo("WAIT_SPLIT");
|
||||
assertThat(result.feeStatus()).isEqualTo("CALCULATED");
|
||||
assertThat(result.initialPricingSucceeded()).isTrue();
|
||||
assertThat(result.initialFee()).isSameAs(fee);
|
||||
}
|
||||
|
||||
@Test
|
||||
void keepsWaitSplitWhenNoPriceMatches() {
|
||||
when(pricing.calculate(10001L, 9L, 88L, null, "initial-88-30-5"))
|
||||
.thenThrow(new BizException("未匹配有效价格"));
|
||||
|
||||
var result = service.tryCalculate(10001L, 9L, 88L, 30L, 5);
|
||||
|
||||
assertThat(result.processStatus()).isEqualTo("WAIT_SPLIT");
|
||||
assertThat(result.feeStatus()).isEqualTo("UNPRICED");
|
||||
assertThat(result.initialPricingSucceeded()).isFalse();
|
||||
assertThat(result.initialPricingMessage()).contains("未匹配有效价格");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -78,7 +78,7 @@ class MeasurementReviewServiceTest {
|
||||
ArgumentCaptor.forClass(InboundStatusHistoryEntity.class);
|
||||
verify(fixture.repository).insertHistory(history.capture());
|
||||
assertThat(history.getValue().getFromStatus()).isEqualTo("MEASURE_PENDING");
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("WAIT_PRICING");
|
||||
assertThat(history.getValue().getToStatus()).isEqualTo("WAIT_SPLIT");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.baiyue.firstleg.pricing.RegionWeightTierEstimator;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRepository;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelTaxModeRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.ChannelPrefixRow;
|
||||
import com.baiyue.firstleg.repository.PriceBookQueryRows.RateRow;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -41,6 +42,32 @@ class MerchantPriceBookServiceTest {
|
||||
@MockBean private PriceBookWarehouseClient warehouses;
|
||||
@Resource private MerchantPriceBookService service;
|
||||
|
||||
@Test
|
||||
void shouldResolveNormalAndOverseasInboundCodePrefixesFromChannelCatalog() {
|
||||
when(repository.activeVersionId()).thenReturn(7L);
|
||||
when(repository.channels(7L, 10L)).thenReturn(List.of(
|
||||
channel(7L, "MATSON_CLX_TRUCK", PriceModel.FBA_WAREHOUSE_RATE)));
|
||||
when(repository.channelPrefix("MATSON_CLX_TRUCK"))
|
||||
.thenReturn(new ChannelPrefixRow("MATSON_CLX_TRUCK", "ZBYHK", "ZBYHWK"));
|
||||
|
||||
assertThat(service.requireAvailableChannel(10001L, 10L, "matson_clx_truck", "FBA")
|
||||
.inboundCodePrefix()).isEqualTo("ZBYHK");
|
||||
assertThat(service.requireAvailableChannel(10001L, 10L, "MATSON_CLX_TRUCK", "OVERSEAS")
|
||||
.inboundCodePrefix()).isEqualTo("ZBYHWK");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFallBackToNormalPrefixWhenChannelHasNoOverseasVariant() {
|
||||
when(repository.activeVersionId()).thenReturn(7L);
|
||||
when(repository.channels(7L, 10L)).thenReturn(List.of(
|
||||
channel(7L, "OA_HOUSTON_TRUCK", PriceModel.FBA_WAREHOUSE_RATE)));
|
||||
when(repository.channelPrefix("OA_HOUSTON_TRUCK"))
|
||||
.thenReturn(new ChannelPrefixRow("OA_HOUSTON_TRUCK", "HPHK", null));
|
||||
|
||||
assertThat(service.requireAvailableChannel(10001L, 10L, "OA_HOUSTON_TRUCK", "OVERSEAS")
|
||||
.inboundCodePrefix()).isEqualTo("HPHK");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyChannelsWithoutActiveVersion() {
|
||||
when(repository.activeVersionId()).thenReturn(null);
|
||||
|
||||
+29
-4
@@ -41,6 +41,28 @@ class TruckAppointmentServiceTest {
|
||||
|
||||
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
|
||||
|
||||
@Test void detailResolvesStableNamesAndIdentifiersFromLinkedResources() {
|
||||
TruckAppointmentEntity appointment = new TruckAppointmentEntity();
|
||||
appointment.setId(51L); appointment.setOceanBookingId(61L); appointment.setContainerBookingId(71L);
|
||||
when(repository.find(51L)).thenReturn(appointment);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(61L); booking.setBookingNo("OB61");
|
||||
booking.setWarehouseName("Shenzhen Transit"); booking.setShippingCompanyId(81L);
|
||||
booking.setShippingCompanyName("Carrier Line"); booking.setBookingAgentId(91L); booking.setBookingAgentName("Agent Co");
|
||||
when(oceanBookings.find(61L)).thenReturn(booking);
|
||||
ContainerBookingEntity container = new ContainerBookingEntity(); container.setId(71L);
|
||||
container.setCabinetNo("CAB-71"); container.setContainerNo("MSCU1234567");
|
||||
when(oceanBookings.containerBooking(71L)).thenReturn(container);
|
||||
|
||||
TruckAppointmentEntity detail = service.detail(51L);
|
||||
|
||||
assertThat(detail.getOceanBookingNo()).isEqualTo("OB61");
|
||||
assertThat(detail.getWarehouseName()).isEqualTo("Shenzhen Transit");
|
||||
assertThat(detail.getShippingCompanyName()).isEqualTo("Carrier Line");
|
||||
assertThat(detail.getBookingAgentName()).isEqualTo("Agent Co");
|
||||
assertThat(detail.getCabinetNo()).isEqualTo("CAB-71");
|
||||
assertThat(detail.getContainerNo()).isEqualTo("MSCU1234567");
|
||||
}
|
||||
|
||||
@Test void createRequiresZeroVersionAndInitialisesDraft() {
|
||||
SaveRequest invalid = request(1);
|
||||
assertThatThrownBy(() -> service.create(7L, "admin", invalid)).isInstanceOf(BizException.class);
|
||||
@@ -63,10 +85,13 @@ class TruckAppointmentServiceTest {
|
||||
when(request.oceanBookingId()).thenReturn(99L);
|
||||
when(request.customerId()).thenReturn(7L);
|
||||
when(request.loadingPlanId()).thenReturn(null);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED"); booking.setCustomerId(7L);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED"); booking.setCustomerId(7L); booking.setCustomerName("Canonical Customer");
|
||||
when(oceanBookings.find(99L)).thenReturn(booking);
|
||||
service.create(7L, "admin", request);
|
||||
verify(repository).insert(any(TruckAppointmentEntity.class));
|
||||
ArgumentCaptor<TruckAppointmentEntity> entity = ArgumentCaptor.forClass(TruckAppointmentEntity.class);
|
||||
verify(repository).insert(entity.capture());
|
||||
assertThat(entity.getValue().getCustomerId()).isEqualTo(7L);
|
||||
assertThat(entity.getValue().getCustomerName()).isEqualTo("Canonical Customer");
|
||||
verify(repository).history(any(TruckAppointmentHistoryEntity.class));
|
||||
}
|
||||
|
||||
@@ -76,7 +101,7 @@ class TruckAppointmentServiceTest {
|
||||
when(request.oceanBookingId()).thenReturn(99L);
|
||||
when(request.containerBookingId()).thenReturn(88L);
|
||||
when(request.loadingPlanId()).thenReturn(77L);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED");
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED"); booking.setCustomerId(7L); booking.setCustomerName("Canonical Customer");
|
||||
when(oceanBookings.find(99L)).thenReturn(booking);
|
||||
ContainerBookingEntity container = new ContainerBookingEntity(); container.setId(88L);
|
||||
when(oceanBookings.containerBooking(88L)).thenReturn(container);
|
||||
@@ -97,7 +122,7 @@ class TruckAppointmentServiceTest {
|
||||
when(request.oceanBookingId()).thenReturn(99L);
|
||||
when(request.containerBookingId()).thenReturn(88L);
|
||||
when(request.loadingPlanId()).thenReturn(77L);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED");
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setId(99L); booking.setStatus("RELEASED"); booking.setCustomerId(7L); booking.setCustomerName("Canonical Customer");
|
||||
when(oceanBookings.find(99L)).thenReturn(booking);
|
||||
ContainerBookingEntity container = new ContainerBookingEntity(); container.setId(88L);
|
||||
when(oceanBookings.containerBooking(88L)).thenReturn(container);
|
||||
|
||||
Reference in New Issue
Block a user