This commit is contained in:
2026-08-17 14:46:44 +08:00
parent b914bb7532
commit a34b5d5ba0
38 changed files with 249 additions and 115 deletions
@@ -19,13 +19,13 @@ class WarehouseSupplierClientContractTest {
FeignClient annotation = WarehouseSupplierClient.class.getAnnotation(FeignClient.class);
assertThat(annotation.path()).isEqualTo("/internal/warehouse/v1/suppliers");
Method method = WarehouseSupplierClient.class.getDeclaredMethod("get", String.class, long.class,
boolean.class, String.class, String.class);
boolean.class, String.class);
assertThat(method.getAnnotation(GetMapping.class).value()).containsExactly("/{supplierId}");
assertThat(method.getParameterAnnotations()[0][0]).isInstanceOf(RequestHeader.class);
assertThat(((RequestHeader) method.getParameterAnnotations()[0][0]).value())
.isEqualTo("X-Internal-Service-Token");
assertThat(method.getParameterAnnotations()[3][0]).isInstanceOf(RequestParam.class);
assertThat(method.getParameterAnnotations()[4][0]).isInstanceOf(RequestParam.class);
assertThat(method.getParameterCount()).isEqualTo(4);
}
@Test
@@ -33,7 +33,9 @@ class WarehouseSupplierClientContractTest {
String ocean = Files.readString(Path.of("src/main/resources/openapi/ocean-bookings-v1.yaml"), StandardCharsets.UTF_8);
String truck = Files.readString(Path.of("src/main/resources/openapi/truck-appointments-v1.yaml"), StandardCharsets.UTF_8);
String customs = Files.readString(Path.of("src/main/resources/openapi/customs-v1.yaml"), StandardCharsets.UTF_8);
assertThat(ocean).contains("bookingAgentId:", "BOOKING", "INTERNATIONAL", "persisted snapshot always comes from Warehouse");
assertThat(ocean).contains("bookingAgentId:", "category BOOKING",
"do not use domestic/international scope", "persisted snapshot always comes from Warehouse")
.doesNotContain("BOOKING with INTERNATIONAL scope");
assertThat(truck).contains("carrierId:", "TRUCKING", "persisted snapshot comes from Warehouse");
assertThat(customs).contains("customsAgentId", "CUSTOMS", "free text cannot bypass supplier validation");
}
@@ -36,6 +36,9 @@ class PdaConvergenceOpenApiContractTest {
.contains("evidence identity is taskId+inboundId+loadingPlanId+containerBookingId")
.contains("Current fl_inbound_order.version for inboundId")
.contains("Compatibility alias of inboundVersion from the same fl_inbound_order row")
.contains("sourceType", "recordStatus", "mergedIntoContainerBookingId")
.contains("Task detail is served only from the ACTIVE container booking")
.contains("Enforced by the server-side pending-task query")
.contains("CompleteLoadingRequest");
String taskLookup = yaml.substring(
@@ -47,6 +50,10 @@ class PdaConvergenceOpenApiContractTest {
assertThat(taskLookup)
.contains("name: cabinetNo", "name: loadingPlanId")
.doesNotContain("name: containerNo");
String taskDetail = yaml.substring(
yaml.indexOf(" /api/platform/v1/first-leg/pda-loading/tasks/{taskId}:"),
yaml.indexOf(" /api/platform/v1/first-leg/pda-loading/tasks/{taskId}/claim:"));
assertThat(taskDetail).contains("#/components/schemas/Task", "ACTIVE container lifecycle fields");
assertThat(batchRequest)
.contains("loadingPlanId:", "containerBookingId:")
.doesNotContain("containerNo:");
@@ -34,7 +34,7 @@ class CustomsServiceTest {
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
ReflectionTestUtils.setField(service, "internalToken", "token");
when(suppliers.customs(anyLong(), anyString()))
.thenReturn(new com.baiyue.firstleg.client.WarehouseSupplierClient.Snapshot(88L, "线下报关代理", "CUSTOMS", null, "ACTIVE"));
.thenReturn(new com.baiyue.firstleg.client.WarehouseSupplierClient.Snapshot(88L, "线下报关代理", "CUSTOMS", "ACTIVE"));
when(repository.completeOperation(anyLong(), anyString(), anyLong())).thenReturn(1);
}
@@ -29,6 +29,9 @@ class LoadingPickPdaTaskSqlContractTest {
assertThat(sql)
.contains("task.task_status = 'LOADING'")
.contains("booking.record_status = 'ACTIVE'")
.contains("booking.source_type AS sourceType")
.contains("booking.record_status AS recordStatus")
.contains("booking.merged_into_container_booking_id AS mergedIntoContainerBookingId")
.contains("assignment.pick_task_id IS NULL OR assignment.assignee_id = #{operatorId}")
.contains("booking.warehouse_id AS warehouseId")
.doesNotContain("tenantId");
@@ -62,7 +62,7 @@ class ContainerServiceTest {
when(warehouses.active(anyString(), anyLong(), anyLong())).thenAnswer(invocation ->
activeWarehouse(invocation.getArgument(2, Long.class), "DOMESTIC", "上海中转仓"));
when(suppliers.trucking(202L, "拖车公司A"))
.thenReturn(new WarehouseSupplierClient.Snapshot(202L, "拖车公司A", "TRUCKING", null, "ACTIVE"));
.thenReturn(new WarehouseSupplierClient.Snapshot(202L, "拖车公司A", "TRUCKING", "ACTIVE"));
}
@Test
@@ -17,6 +17,7 @@ import com.baiyue.firstleg.entity.LoadingPickItemEntity;
import com.baiyue.firstleg.entity.LoadingPickOperationEntity;
import com.baiyue.firstleg.entity.LoadingPickTaskEntity;
import com.baiyue.firstleg.entity.LoadingPlanItemEntity;
import com.baiyue.firstleg.entity.ContainerBookingEntity;
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
import com.baiyue.firstleg.loading.mapper.LoadingInboundMapper;
import com.baiyue.firstleg.loading.bridge.dto.LoadingModelBridgeDtos.Link;
@@ -30,6 +31,7 @@ import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEn
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.mockito.InOrder;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@@ -70,6 +72,16 @@ class LoadingPickServiceTest {
@Resource
private LoadingPickService service;
@BeforeEach
void activeOperationalContainer() {
ContainerBookingEntity booking = new ContainerBookingEntity();
booking.setId(99L);
booking.setContainerNo("CSLU1234567");
booking.setSourceType("SIMULATION");
booking.setRecordStatus("ACTIVE");
when(smartLoadingRepository.booking(99L)).thenReturn(booking);
}
@Test
void opensTaskOnlyAfterReleasedBookingAndRealContainerNumber() {
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
@@ -212,6 +224,7 @@ class LoadingPickServiceTest {
task.setContainerBookingId(40L); task.setCabinetNo("CAB-001");
var booking = new com.baiyue.firstleg.entity.ContainerBookingEntity();
booking.setId(40L); booking.setContainerNo("MSCU-NEW");
booking.setSourceType("SIMULATION"); booking.setRecordStatus("ACTIVE");
when(repository.taskByCabinet("CAB-001", 101L)).thenReturn(task);
when(repository.taskByPlan(10L, 101L)).thenReturn(task);
when(smartLoadingRepository.booking(40L)).thenReturn(booking);
@@ -224,6 +237,53 @@ class LoadingPickServiceTest {
assertThat(byCabinet.id()).isEqualTo(20L);
assertThat(byPlan.id()).isEqualTo(20L);
assertThat(byCabinet.currentContainerNo()).isEqualTo("MSCU-NEW");
assertThat(byCabinet.sourceType()).isEqualTo("SIMULATION");
assertThat(byCabinet.recordStatus()).isEqualTo("ACTIVE");
assertThat(byCabinet.mergedIntoContainerBookingId()).isNull();
}
@Test
void pendingTasksExposeServerFilteredContainerLifecycle() {
var row = new com.baiyue.firstleg.mapper.LoadingPickTaskMapper.PendingTaskRow();
row.setTaskId(20L);
row.setLoadingPlanId(10L);
row.setContainerBookingId(99L);
row.setCabinetNo("CAB-001");
row.setContainerNo("MSCU-001");
row.setSourceType("SIMULATION");
row.setRecordStatus("ACTIVE");
row.setMergedIntoContainerBookingId(null);
row.setWarehouseId(101L);
row.setTaskStatus("LOADING");
row.setWorkflowStatus("UNASSIGNED");
row.setVersion(0);
row.setAssignmentVersion(0);
row.setItemCount(1);
row.setPendingItemCount(1);
when(repository.pendingTasks(9L, null)).thenReturn(List.of(row));
var result = service.pendingTasks(9L, null).get(0);
assertThat(result.sourceType()).isEqualTo("SIMULATION");
assertThat(result.recordStatus()).isEqualTo("ACTIVE");
assertThat(result.mergedIntoContainerBookingId()).isNull();
}
@Test
void taskDetailRejectsMergedContainerSource() {
LoadingPickTaskEntity task = task();
ContainerBookingEntity merged = new ContainerBookingEntity();
merged.setId(99L);
merged.setSourceType("OCEAN_BOOKING");
merged.setRecordStatus("MERGED");
merged.setMergedIntoContainerBookingId(88L);
when(repository.task(20L)).thenReturn(task);
when(smartLoadingRepository.booking(99L)).thenReturn(merged);
when(repository.items(20L, 101L)).thenReturn(List.of());
when(repository.cartonLabels(20L)).thenReturn(List.of());
assertThatThrownBy(() -> service.task(20L, 101L))
.isInstanceOf(BizException.class).hasMessageContaining("已非有效存续记录");
}
@Test
@@ -345,6 +405,9 @@ class LoadingPickServiceTest {
assertThat(result.loadingVersion()).isEqualTo(7);
assertThat(result.loadingPlanId()).isEqualTo(10L);
assertThat(result.containerBookingId()).isEqualTo(99L);
assertThat(result.sourceType()).isEqualTo("SIMULATION");
assertThat(result.recordStatus()).isEqualTo("ACTIVE");
assertThat(result.mergedIntoContainerBookingId()).isNull();
}
@Test
@@ -34,19 +34,29 @@ class WarehouseSupplierSnapshotServiceTest {
}
@Test
void resolvesActiveBookingSupplierWithFixedScopeAndServerName() {
when(client.get("internal-token", 7L, true, "BOOKING", "INTERNATIONAL"))
.thenReturn(ApiResponse.success(new WarehouseSupplierClient.Snapshot(7L, "海运代理", "BOOKING", "INTERNATIONAL", "ACTIVE")));
void resolvesActiveBookingSupplierByCategoryWithoutScopeAndUsesServerName() {
when(client.get("internal-token", 7L, true, "BOOKING"))
.thenReturn(ApiResponse.success(new WarehouseSupplierClient.Snapshot(7L, "海运代理", "BOOKING", "ACTIVE")));
var snapshot = service.booking(7L, "海运代理");
assertThat(snapshot.name()).isEqualTo("海运代理");
verify(client).get("internal-token", 7L, true, "BOOKING", "INTERNATIONAL");
verify(client).get("internal-token", 7L, true, "BOOKING");
}
@Test
void rejectsClientNameThatDiffersFromWarehouseSnapshot() {
when(client.get(anyString(), eq(9L), eq(true), eq("CUSTOMS"), isNull()))
.thenReturn(ApiResponse.success(new WarehouseSupplierClient.Snapshot(9L, "真实报关行", "CUSTOMS", null, "ACTIVE")));
when(client.get(anyString(), eq(9L), eq(true), eq("CUSTOMS")))
.thenReturn(ApiResponse.success(new WarehouseSupplierClient.Snapshot(9L, "真实报关行", "CUSTOMS", "ACTIVE")));
assertThatThrownBy(() -> service.customs(9L, "伪造名称"))
.isInstanceOf(BizException.class).hasMessageContaining("不一致");
}
@Test
void rejectsSupplierSnapshotWithWrongCategoryOrInactiveStatus() {
when(client.get("internal-token", 7L, true, "BOOKING"))
.thenReturn(ApiResponse.success(new WarehouseSupplierClient.Snapshot(
7L, "错误代理", "TRUCKING", "ACTIVE")));
assertThatThrownBy(() -> service.booking(7L, null))
.isInstanceOf(BizException.class).hasMessageContaining("类别不匹配");
}
}