Test #1

Merged
admin merged 1726 commits from test into main 2026-09-17 11:52:45 +08:00
4 changed files with 67 additions and 3 deletions
Showing only changes of commit 9098d8dfdf - Show all commits
@@ -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', () => {