test: cover loading reassignment and PDA completion gates

This commit is contained in:
2026-09-16 11:40:57 +08:00
parent 011aa80870
commit dcd8cc2c85
2 changed files with 90 additions and 0 deletions
@@ -18,6 +18,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -328,6 +332,57 @@ class LoadingModelBridgeServiceTest {
anyInt(), anyLong(), anyString());
}
@ParameterizedTest
@ValueSource(ints = {1, 3})
void reassignAfterOneOrRepeatedFullRemovalsCanComplete(int removals) {
Map<Long, PreloadLoadingPlanLinkEntity> links = new LinkedHashMap<>();
Map<Long, Integer> quantities = new LinkedHashMap<>();
Map<Long, String> statuses = new LinkedHashMap<>();
when(repository.byPair(eq(10L), eq(20L), anyLong()))
.thenAnswer(call -> links.get(call.getArgument(2, Long.class)));
when(repository.allocatedPackages(eq(10L), eq(20L), anyLong()))
.thenAnswer(call -> quantities.get(call.getArgument(2, Long.class)));
when(repository.loadingPlan(anyLong())).thenAnswer(call -> {
long plan = call.getArgument(0);
return new LoadingPlanSnapshot(plan, plan + 100, "C" + plan, statuses.get(plan), 1);
});
when(repository.preload(10L, 20L)).thenReturn(new PreloadSnapshot(30L, 10L, 20L, "CONFIRMED", 1));
doAnswer(call -> {
PreloadLoadingPlanLinkEntity link = call.getArgument(0);
links.put(link.getLoadingPlanId(), link);
return null;
}).when(repository).insert(any());
doAnswer(call -> {
PreloadLoadingPlanLinkEntity link = call.getArgument(0);
links.remove(link.getLoadingPlanId());
return null;
}).when(repository).delete(any());
when(repository.byInbound(10L, 20L)).thenAnswer(call -> links.keySet().stream()
.map(plan -> new Link(plan, 10L, 20L, 30L, "CONFIRMED", 1, plan, plan + 100,
"C" + plan, statuses.get(plan), 1, quantities.get(plan), LocalDateTime.now())).toList());
for (long plan = 40; plan < 40 + removals; plan++) {
quantities.put(plan, 8);
statuses.put(plan, "EDITING");
service.bindIncrementalAllocation(plan, 10L, 20L, 7L, "admin", "assign-" + plan);
assertThat(links).containsKey(plan);
quantities.put(plan, 3);
service.removeIfUnallocated(plan, 10L, 20L);
assertThat(links).containsKey(plan);
quantities.put(plan, 0);
service.removeIfUnallocated(plan, 10L, 20L);
assertThat(links).isEmpty();
}
quantities.put(90L, 8);
statuses.put(90L, "LOADING");
service.bindIncrementalAllocation(90L, 10L, 20L, 7L, "admin", "assign-final");
service.requireStarted(10L, 20L);
statuses.put(90L, "COMPLETED");
service.requireCompleted(10L, 20L);
assertThat(links).containsOnlyKeys(90L);
verify(repository, times(removals)).delete(any());
}
private static Link link(String preloadStatus, int preloadVersion, String loadingStatus,
int loadingVersion, int packages) {
return new Link(1L, 10L, 20L, 30L, preloadStatus, preloadVersion, 40L, 50L, "C1",
@@ -55,6 +55,7 @@ class LoadingDeliveryServiceTest {
@Test
void pdaFinalizeUsesSourceVersionIncrementedByFullSettlement() throws Exception {
useRealBridgeWithRemovedAndCurrentAllocations(0);
Snapshot refreshed = new Snapshot(20L, 10L, "LOADING", 8);
when(repository.completionTarget(10L, 20L)).thenReturn(null);
when(repository.findInboundForUpdate(10L, 20L)).thenReturn(refreshed);
@@ -66,11 +67,45 @@ class LoadingDeliveryServiceTest {
when(repository.completeLoading(10L, 20L, 9L, 8)).thenReturn(1);
when(objectMapper.writeValueAsString(org.mockito.ArgumentMatchers.any())).thenReturn("result");
service.requirePdaCompletionReady(10L, 20L);
service.completeAfterPdaSettlement(10L, 20L, 9L, "operator", "pda-full", "done");
verify(repository).completeLoading(10L, 20L, 9L, 8);
}
@Test
void pdaPreflightBlocksAnUnstartedContainerWithRealAllocationBeforeAnyCompletionWrite() {
useRealBridgeWithRemovedAndCurrentAllocations(3);
when(repository.findInbound(10L, 20L)).thenReturn(new Snapshot(20L, 10L, "LOADING", 8));
assertThatThrownBy(() -> service.requirePdaCompletionReady(10L, 20L))
.hasMessageContaining("关联柜次尚未全部开始装柜");
verify(repository, never()).completeLoading(
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(),
org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyInt());
verify(completionGate, never()).requireReleasedBooking(10L, 20L);
}
/** Exercise the production bridge gate, rather than mocking its decision. */
private void useRealBridgeWithRemovedAndCurrentAllocations(int oldContainerPackages) {
var bridgeRepository = org.mockito.Mockito.mock(
com.baiyue.firstleg.loading.bridge.repository.LoadingModelBridgeRepository.class);
var realBridge = new LoadingModelBridgeService();
org.springframework.test.util.ReflectionTestUtils.setField(realBridge, "repository", bridgeRepository);
when(bridgeRepository.byInbound(10L, 20L)).thenReturn(java.util.List.of(
new com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link(
1L, 10L, 20L, 30L, "CONFIRMED", 1, 40L, 50L, null,
"EDITING", 1, oldContainerPackages, java.time.LocalDateTime.now()),
new com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link(
2L, 10L, 20L, 30L, "CONFIRMED", 1, 41L, 99L, "C1",
"LOADING", 1, 2, java.time.LocalDateTime.now())));
org.mockito.Mockito.doAnswer(call -> {
realBridge.requireStarted(10L, 20L);
return null;
}).when(loadingModelBridge).requireStarted(10L, 20L);
}
@Test
void pdaFinalizeLocksAndUsesLoadedSliceVersionAfterPartialSettlement() throws Exception {
var target = new com.baiyue.firstleg.loading.mapper.LoadingInboundMapper.CompletionTarget(