feat(ft546): adapt existing WMS contract to WCS core
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
package com.ruoyi.project.ft546.wms;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.ruoyi.api.controller.Wcs2WmsController;
|
||||
import com.ruoyi.api.domain.RequestLog;
|
||||
import com.ruoyi.api.domain.wcs2wms.JobParam;
|
||||
import com.ruoyi.api.domain.wcs2wms.ReceiveJob;
|
||||
import com.ruoyi.api.service.IApiService;
|
||||
import com.ruoyi.api.service.impl.RequestLogServiceImpl;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.project.ft546.config.Ft546WcsProperties;
|
||||
import com.ruoyi.wcs.application.task.CreateTransportOrderCommand;
|
||||
import com.ruoyi.wcs.application.task.TransportOrderResult;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrder;
|
||||
import com.ruoyi.wcs.domain.task.TransportOrderType;
|
||||
import com.ruoyi.wcs.port.in.TransportOrderUseCase;
|
||||
import com.ruoyi.wcs.port.out.WmsNotification;
|
||||
import com.ruoyi.wcs.port.out.WmsNotificationReceipt;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class Ft546WmsAdapterServiceTest {
|
||||
private static final Instant NOW = Instant.parse("2026-08-11T06:00:00Z");
|
||||
|
||||
@Test
|
||||
void mapsExistingInboundRequestToCanonicalOrder() {
|
||||
Ft546WmsOrderMapper mapper = new Ft546WmsOrderMapper();
|
||||
ReceiveJob job = job("TASK-1");
|
||||
job.setEntranceId("PORT-1");
|
||||
job.setCargoSpaceId("SPACE-9");
|
||||
|
||||
CreateTransportOrderCommand command = mapper.inbound(job);
|
||||
|
||||
assertEquals("TASK-1", command.getExternalRequestId());
|
||||
assertEquals(TransportOrderType.INBOUND, command.getOrderType());
|
||||
assertEquals("PORT-1", command.getSourceCode());
|
||||
assertEquals("SPACE-9", command.getDestinationCode());
|
||||
assertEquals("TRAY-1", command.getLoadId());
|
||||
assertEquals("JOB-1", command.getAttributes().get("jobId"));
|
||||
assertEquals("TASK-1",
|
||||
command.getAttributes().get("legacyTaskNo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsExistingOutboundRequestToCanonicalOrder() {
|
||||
Ft546WmsOrderMapper mapper = new Ft546WmsOrderMapper();
|
||||
ReceiveJob job = job("TASK-2");
|
||||
job.setCargoSpaceId("SPACE-9");
|
||||
job.setExitId("PORT-2");
|
||||
|
||||
CreateTransportOrderCommand command = mapper.outbound(job);
|
||||
|
||||
assertEquals(TransportOrderType.OUTBOUND, command.getOrderType());
|
||||
assertEquals("SPACE-9", command.getSourceCode());
|
||||
assertEquals("PORT-2", command.getDestinationCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateWmsRequestReturnsTheSameOrder() {
|
||||
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
|
||||
Ft546WmsAdapterService service = new Ft546WmsAdapterService(useCase,
|
||||
() -> Collections.singletonList("SNAPSHOT"));
|
||||
ReceiveJob job = job("TASK-1");
|
||||
job.setEntranceId("PORT-1");
|
||||
job.setCargoSpaceId("SPACE-9");
|
||||
|
||||
TransportOrderResult first = service.receiveInMydoJob(job);
|
||||
TransportOrderResult duplicate = service.receiveInMydoJob(job);
|
||||
|
||||
assertEquals(first.getOrderId(), duplicate.getOrderId());
|
||||
assertEquals(2, useCase.createCommands.size());
|
||||
assertEquals("TASK-1", useCase.createCommands.get(1)
|
||||
.getExternalRequestId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void controllerUsesStandardAdapterOnlyInStandardMode() {
|
||||
IApiService legacy = mock(IApiService.class);
|
||||
AjaxResult legacyResult = AjaxResult.success("legacy");
|
||||
ReceiveJob job = job("TASK-1");
|
||||
job.setEntranceId("PORT-1");
|
||||
job.setCargoSpaceId("SPACE-9");
|
||||
when(legacy.receiveInMydoJob(job)).thenReturn(legacyResult);
|
||||
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
|
||||
Ft546WmsAdapterService standard = new Ft546WmsAdapterService(useCase,
|
||||
() -> Collections.singletonList("SNAPSHOT"));
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
Wcs2WmsController controller = new Wcs2WmsController(legacy,
|
||||
standard, properties);
|
||||
|
||||
assertSame(legacyResult, controller.receiveInMydoJob(job));
|
||||
verify(legacy).receiveInMydoJob(job);
|
||||
|
||||
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
AjaxResult standardResult = controller.receiveInMydoJob(job);
|
||||
|
||||
assertTrue(standardResult.isSuccess());
|
||||
assertTrue(standardResult.get(AjaxResult.DATA_TAG)
|
||||
instanceof TransportOrderResult);
|
||||
assertEquals(1, useCase.createCommands.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void controllerDelegatesCancelAndSnapshotWithoutLegacyWritesInStandard() {
|
||||
IApiService legacy = mock(IApiService.class);
|
||||
FakeTransportOrderUseCase useCase = new FakeTransportOrderUseCase();
|
||||
Ft546WmsAdapterService standard = new Ft546WmsAdapterService(useCase,
|
||||
() -> Arrays.asList("S1", "S2"));
|
||||
Ft546WcsProperties properties = new Ft546WcsProperties();
|
||||
properties.setRuntimeMode(Ft546WcsProperties.RuntimeMode.STANDARD);
|
||||
Wcs2WmsController controller = new Wcs2WmsController(legacy,
|
||||
standard, properties);
|
||||
JobParam cancel = new JobParam("JOB-1", "TRAY-1", "TASK-1");
|
||||
|
||||
AjaxResult cancelResult = controller.cancelMydoJob(cancel);
|
||||
AjaxResult snapshotResult = controller.tidyVehicleSnapshot();
|
||||
|
||||
assertTrue(cancelResult.isSuccess());
|
||||
assertEquals(Arrays.asList("S1", "S2"),
|
||||
snapshotResult.get(AjaxResult.DATA_TAG));
|
||||
assertEquals(Collections.singletonList("TASK-1"),
|
||||
useCase.cancelledExternalIds);
|
||||
verify(legacy, never()).cancelMydoJob(any(JobParam.class));
|
||||
verify(legacy, never()).tidyVehicleSnapshot();
|
||||
}
|
||||
|
||||
@Test
|
||||
void notificationUsesEventIdAsLegacyCallbackIdempotencyKey() {
|
||||
FakeRequestLogService requestLogs = new FakeRequestLogService();
|
||||
Ft546WmsNotificationGateway gateway =
|
||||
new Ft546WmsNotificationGateway(requestLogs);
|
||||
Map<String, String> context = new LinkedHashMap<String, String>();
|
||||
context.put("jobId", "JOB-1");
|
||||
context.put("inOrOut", "10");
|
||||
WmsNotification notification = new WmsNotification("EVENT-1",
|
||||
"TASK-1", "ORDER-1", NOW, "OrderCompleted", "OK",
|
||||
"completed", context);
|
||||
|
||||
WmsNotificationReceipt receipt = gateway.notify(notification);
|
||||
|
||||
assertEquals(WmsNotificationReceipt.Status.DELIVERED,
|
||||
receipt.getStatus());
|
||||
JSONObject payload = JSONUtil.parseObj(requestLogs.requestBody);
|
||||
assertEquals("EVENT-1", payload.getStr("eventId"));
|
||||
assertEquals("JOB-1", payload.getStr("jobId"));
|
||||
assertEquals("TASK-1", payload.getStr("taskNo"));
|
||||
assertEquals("10", payload.getStr("wmsType"));
|
||||
assertEquals("OrderCompleted", payload.getStr("eventType"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void notificationTransportFailureRemainsRetryable() {
|
||||
FakeRequestLogService requestLogs = new FakeRequestLogService();
|
||||
requestLogs.success = false;
|
||||
Ft546WmsNotificationGateway gateway =
|
||||
new Ft546WmsNotificationGateway(requestLogs);
|
||||
WmsNotification notification = new WmsNotification("EVENT-2",
|
||||
"TASK-2", "ORDER-2", NOW, "OrderFailed", "FAILED",
|
||||
"fault", Collections.<String, String>emptyMap());
|
||||
|
||||
WmsNotificationReceipt receipt = gateway.notify(notification);
|
||||
|
||||
assertEquals(WmsNotificationReceipt.Status.RETRYABLE_FAILURE,
|
||||
receipt.getStatus());
|
||||
}
|
||||
|
||||
private static ReceiveJob job(String taskNo) {
|
||||
ReceiveJob job = new ReceiveJob();
|
||||
job.setTaskNo(taskNo);
|
||||
job.setJobId("JOB-1");
|
||||
job.setTrayCode("TRAY-1");
|
||||
job.setInOrOut("10");
|
||||
job.setRoadwayId("R1");
|
||||
job.setClassId("7");
|
||||
job.setPriority(5L);
|
||||
return job;
|
||||
}
|
||||
|
||||
private static final class FakeTransportOrderUseCase
|
||||
implements TransportOrderUseCase {
|
||||
private final Map<String, TransportOrderResult> orders =
|
||||
new LinkedHashMap<String, TransportOrderResult>();
|
||||
private final List<CreateTransportOrderCommand> createCommands =
|
||||
new ArrayList<CreateTransportOrderCommand>();
|
||||
private final List<String> cancelledExternalIds =
|
||||
new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
public TransportOrderResult create(CreateTransportOrderCommand command) {
|
||||
createCommands.add(command);
|
||||
TransportOrderResult existing = orders.get(
|
||||
command.getExternalRequestId());
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
TransportOrder order = TransportOrder.accept(
|
||||
"ORDER-" + command.getExternalRequestId(),
|
||||
command.getExternalRequestId(), command.getOrderType(),
|
||||
command.getLoadId(), command.getSourceCode(),
|
||||
command.getDestinationCode(), command.getPriority(), NOW);
|
||||
order.plan("WORKFLOW-" + command.getExternalRequestId());
|
||||
TransportOrderResult result = TransportOrderResult.from(order);
|
||||
orders.put(command.getExternalRequestId(), result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult cancel(String externalRequestId) {
|
||||
cancelledExternalIds.add(externalRequestId);
|
||||
TransportOrderResult existing = orders.get(externalRequestId);
|
||||
if (existing != null) {
|
||||
return existing;
|
||||
}
|
||||
CreateTransportOrderCommand command =
|
||||
new CreateTransportOrderCommand(externalRequestId,
|
||||
TransportOrderType.INBOUND, null, "UNKNOWN-SOURCE",
|
||||
"UNKNOWN-TARGET", 0, null,
|
||||
Collections.<String, String>emptyMap());
|
||||
return create(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult suspend(String orderId, String reason) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportOrderResult resume(String orderId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FakeRequestLogService
|
||||
extends RequestLogServiceImpl {
|
||||
private boolean success = true;
|
||||
private String requestBody;
|
||||
|
||||
@Override
|
||||
public RequestLog getRequestLog(String url, String endpoint,
|
||||
String body) {
|
||||
this.requestBody = body;
|
||||
RequestLog log = new RequestLog();
|
||||
log.setIsSuccessful(success ? "1" : "0");
|
||||
log.setErrorDetails(success ? null : "injected failure");
|
||||
return log;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user