Test #1
@@ -40,6 +40,8 @@ import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -785,6 +787,7 @@ class LoadingPickServiceTest {
|
||||
|
||||
@Test
|
||||
void failedSameOperationIdCanResumeWithoutCreatingAnotherCommand() {
|
||||
assertNoTruckCoupling();
|
||||
LoadingPickTaskEntity failed = task();
|
||||
failed.setTaskStatus("COMPLETION_FAILED");
|
||||
LoadingPickTaskEntity completing = task();
|
||||
@@ -871,6 +874,35 @@ class LoadingPickServiceTest {
|
||||
verify(completionCommands).complete(900L, "worker-1");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "truck state {0} never participates in PDA completion")
|
||||
@ValueSource(strings = {"NO_APPOINTMENT", "DRAFT", "IN_PROGRESS", "COMPLETED"})
|
||||
void completionWorkerIsIndependentOfEveryTruckAppointmentState(String truckState) {
|
||||
assertNoTruckCoupling();
|
||||
LoadingPickTaskEntity completing = task();
|
||||
completing.setTaskStatus("COMPLETING");
|
||||
when(repository.task(20L)).thenReturn(completing);
|
||||
when(repository.inboundOwners(20L)).thenReturn(List.of(
|
||||
new LoadingPickItemMapper.InboundOwner(101L, 50L)));
|
||||
when(repository.allPlanItemsPicked(10L)).thenReturn(true);
|
||||
when(repository.review(20L)).thenReturn(null);
|
||||
when(repository.approve(20L, 9L, "operator", "checked")).thenReturn(1);
|
||||
when(repository.completeAccepted(20L, 9L, "operator")).thenReturn(1);
|
||||
long commandId = 920L + Math.abs(truckState.hashCode() % 20);
|
||||
when(completionCommands.complete(commandId, "worker-truck-independent")).thenReturn(true);
|
||||
|
||||
service.processCompletion(completionCommand(commandId,
|
||||
"async-truck-independent-" + truckState.toLowerCase()), "worker-truck-independent");
|
||||
|
||||
verify(cabinetEvidenceService).requireComplete(99L);
|
||||
verify(settlementService).settleOwner(20L, 50L, 9L, "operator");
|
||||
verify(loadingDeliveryService).completeAfterPdaSettlement(101L, 50L, 9L, "operator",
|
||||
"PDA-async-truck-independent-" + truckState.toLowerCase() + "-50", "checked");
|
||||
verify(loadingListService).finishFromPda(10L, 20L, 9L, "operator");
|
||||
verify(loadingListService).publishCompletedEvent(10L);
|
||||
verify(repository).completeAccepted(20L, 9L, "operator");
|
||||
verify(completionCommands).complete(commandId, "worker-truck-independent");
|
||||
}
|
||||
|
||||
@Test
|
||||
void workerUsesCabinetEvidenceOnceWithReversedTwoCustomerOwnerOrder() {
|
||||
LoadingPickTaskEntity completing = task();
|
||||
@@ -911,6 +943,7 @@ class LoadingPickServiceTest {
|
||||
|
||||
@Test
|
||||
void secondCustomerOwnerReadyFailureLeavesEveryOwnerUnsettledAndUncompleted() {
|
||||
assertNoTruckCoupling();
|
||||
LoadingPickTaskEntity completing = task();
|
||||
completing.setTaskStatus("COMPLETING");
|
||||
when(repository.task(20L)).thenReturn(completing);
|
||||
@@ -939,6 +972,7 @@ class LoadingPickServiceTest {
|
||||
|
||||
@Test
|
||||
void workerRestartResumesFromOwnerCheckpointsWithoutRepeatingSettlements() {
|
||||
assertNoTruckCoupling();
|
||||
LoadingPickTaskEntity completing = task();
|
||||
completing.setTaskStatus("COMPLETING");
|
||||
when(repository.task(20L)).thenReturn(completing);
|
||||
@@ -1180,6 +1214,14 @@ class LoadingPickServiceTest {
|
||||
verify(repository, never()).touchTask(any(Long.class), any(Integer.class));
|
||||
}
|
||||
|
||||
private static void assertNoTruckCoupling() {
|
||||
assertThat(LoadingPickService.class.getDeclaredFields())
|
||||
.noneMatch(field -> field.getType().equals(TruckAppointmentService.class));
|
||||
assertThat(LoadingPickService.class.getDeclaredMethods())
|
||||
.noneMatch(method -> method.getName().equals("completeTruckAfterCommit")
|
||||
|| method.getName().equals("completeLoadingFromPda"));
|
||||
}
|
||||
|
||||
private void preparePick(LoadingPickTaskEntity task, LoadingPickItemEntity item, String operationId) {
|
||||
when(repository.history(operationId)).thenReturn(null);
|
||||
when(repository.task(20L)).thenReturn(task);
|
||||
|
||||
@@ -120,7 +120,7 @@ test('HTTP chain refreshes after stale inventory and keeps one stable PDA identi
|
||||
assert.doesNotMatch(loading, /\/loading\/complete\?tenantId=/)
|
||||
})
|
||||
|
||||
test('downstream finalizer uses the frozen truck-coupled complete contract', () => {
|
||||
test('downstream finalizer uses the frozen loading complete contract without truck coupling', () => {
|
||||
assert.match(convergence, /loadingPlan\?\.status === 'LOADING'/)
|
||||
assert.match(convergence, /!operations\.some\(\(item\) => item\.actionType === 'COMPLETE_LOADING'\)/)
|
||||
assert.match(customs, /\['AWAITING_REVIEW', 'LOADED'\]/)
|
||||
@@ -136,6 +136,8 @@ test('downstream finalizer uses the frozen truck-coupled complete contract', ()
|
||||
assert.doesNotMatch(finalize, /BLOCKED_CONTRACT/)
|
||||
assert.doesNotMatch(finalize, /truck\?\.finalStatus === 'COMPLETED'/)
|
||||
assert.doesNotMatch(finalize, /\/loading\/complete\?tenantId=/)
|
||||
assert.doesNotMatch(loadingPickService,
|
||||
/TruckAppointmentService|completeLoadingFromPda|completeTruckAfterCommit/)
|
||||
})
|
||||
|
||||
test('PDA complete OpenAPI freezes reliable async acceptance and checkpoint recovery', () => {
|
||||
@@ -215,11 +217,12 @@ test('worker final Firstleg convergence is one short transaction after remote ow
|
||||
'settlementService.settleOwner(',
|
||||
'loadingDeliveryService.completeAfterPdaSettlement(',
|
||||
'completionTransaction.executeWithoutResult',
|
||||
'completeTruckAfterCommit(',
|
||||
])
|
||||
const finalizer = loadingPickService.slice(loadingPickService.indexOf('private void finalizeCompletion'),
|
||||
loadingPickService.indexOf('private void requireColoadConservation'))
|
||||
for (const token of ['COMPLETE_LOADING', 'finishFromPda', 'syncInboundProjections',
|
||||
'publishCompletedEvent', 'completeAccepted', 'completionCommands.complete']) assert.match(finalizer, new RegExp(token))
|
||||
assert.doesNotMatch(finalizer, /settlementService|completeTruckAfterCommit/)
|
||||
assert.doesNotMatch(finalizer, /settlementService/)
|
||||
assert.doesNotMatch(loadingPickService,
|
||||
/TruckAppointmentService|completeLoadingFromPda|completeTruckAfterCommit/)
|
||||
})
|
||||
|
||||
@@ -917,6 +917,9 @@ export async function run(context) {
|
||||
acceptedComplete.http === 200 && codeOf(acceptedComplete) === '200'
|
||||
&& ['PROCESSING', 'FAILED', 'COMPLETED'].includes(acceptedComplete.body?.data?.workflowStatus),
|
||||
`${responseEvidence(acceptedComplete)}, workflow=${acceptedComplete.body?.data?.workflowStatus || 'missing'}`, OPTIONS)
|
||||
context.assert(MODULE, '独立柜未创建拖车单也不阻塞 PDA 装柜完成命令', acceptedComplete,
|
||||
acceptedComplete.http === 200 && codeOf(acceptedComplete) === '200',
|
||||
`containerBookingId=${containerBookingId}, oceanBookingId=${bookingId}, ${responseEvidence(acceptedComplete)}`, OPTIONS)
|
||||
|
||||
let terminalTask = acceptedComplete.body?.data
|
||||
let terminalResponse = acceptedComplete
|
||||
|
||||
@@ -140,6 +140,22 @@ test('late container gate mode proves the final gate without requiring evidence
|
||||
assert.match(value, /nextStage === 'CABINET_EVIDENCE' && nextCode === 'A0409'/)
|
||||
assert.match(value, /!nextMessage\.includes\('集装箱号'\)/)
|
||||
assert.match(value, /context\.createdResources\.phase3LateContainerGate/)
|
||||
assert.match(value, /独立柜未创建拖车单也不阻塞 PDA 装柜完成命令/)
|
||||
})
|
||||
|
||||
test('PDA completion production and HTTP chain never call or gate on truck appointments', () => {
|
||||
const service = source('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java')
|
||||
const serviceTest = source('../../firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java')
|
||||
const loading = source('./phase3-loading.mjs')
|
||||
assert.doesNotMatch(service, /TruckAppointmentService|completeLoadingFromPda|completeTruckAfterCommit/)
|
||||
assert.match(serviceTest, /completionWorkerIsIndependentOfEveryTruckAppointmentState/)
|
||||
for (const state of ['NO_APPOINTMENT', 'DRAFT', 'IN_PROGRESS', 'COMPLETED']) {
|
||||
assert.match(serviceTest, new RegExp(state))
|
||||
}
|
||||
assert.match(serviceTest, /failedSameOperationIdCanResumeWithoutCreatingAnotherCommand[\s\S]*assertNoTruckCoupling/)
|
||||
assert.match(serviceTest, /secondCustomerOwnerReadyFailureLeavesEveryOwnerUnsettledAndUncompleted[\s\S]*assertNoTruckCoupling/)
|
||||
assert.match(serviceTest, /workerRestartResumesFromOwnerCheckpointsWithoutRepeatingSettlements[\s\S]*assertNoTruckCoupling/)
|
||||
assert.doesNotMatch(loading, /\/truck-appointments|completeLoadingFromPda/)
|
||||
})
|
||||
|
||||
test('push replay and both conflict modes preserve the complete pre-PDA snapshot', () => {
|
||||
|
||||
Reference in New Issue
Block a user