chore(lastleg): instrument appointment latency from restored 662dead baseline

This commit is contained in:
2026-10-18 11:43:10 +08:00
parent b0acbfbb34
commit 5cd108b39a
35 changed files with 951 additions and 780 deletions
@@ -11,22 +11,6 @@ import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class AppointmentCargoVerificationBatchTest {
/** 预取只读原料,未被 anyMatch 访问的坏 JSON 不提前报错;下一次准备仍重新读取。 */
@Test void preparedAuthorizedSourcesKeepShortCircuitAndBoundedReads() {
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentCargoVerificationMapper.class);
var service=new AppointmentCargoVerificationService(sources,mapper,new ObjectMapper());
org.springframework.test.util.ReflectionTestUtils.setField(service,"readRepository",new AppointmentCargoReadRepository(mapper));
var resolved=IntStream.rangeClosed(1,201).mapToObj(i->new AppointmentSourceService.Source("ORDER:"+i,"C",(long)i,"IN",null,"CNT","100","仓","FBA:ONT8","ONT8",10,"v1",true,List.of())).toList();
var broken=new AppointmentCargoVerification();broken.setCustomerId("C");broken.setSourceKey("ORDER:2");broken.setPackageCount(10);broken.setShipmentReferencesJson("broken-json");
when(mapper.latestBatch(anyList())).thenAnswer(c->c.<List<AppointmentCoverageService.SourceRef>>getArgument(0).stream().anyMatch(r->r.sourceKey().equals("ORDER:2"))?List.of(broken):List.of());
var read=service.prepareInspection(resolved);
assertTrue(resolved.stream().map(read).anyMatch(c->c.availablePackages()!=null && c.availablePackages()>0));
assertThrows(RuntimeException.class,()->read.apply(resolved.get(1)));
assertEquals(10L,read.apply(resolved.get(200)).availablePackages());
verify(mapper,times(3)).latestBatch(anyList());verify(mapper,times(3)).allocatedBatch(anyList());verifyNoInteractions(sources);
service.prepareInspection(resolved);verify(mapper,times(6)).latestBatch(anyList());verify(mapper,times(6)).allocatedBatch(anyList());
assertThrows(IllegalArgumentException.class,()->read.apply(new AppointmentSourceService.Source("ORDER:1","OTHER",1L,null,null,null,"100",null,"FBA:ONT8","ONT8",10,"v1",true,List.of())));
}
@Test void inspectionUsesTwoBoundedBatchesAndRetainsPerSourcePrerequisitesAndAllocations() {
var sources=mock(AppointmentSourceService.class);var mapper=mock(AppointmentCargoVerificationMapper.class);
var service=new AppointmentCargoVerificationService(sources,mapper,new ObjectMapper());
@@ -146,10 +146,6 @@ class AppointmentPlanningAuthorizationTest {
@Bean AppointmentCargoVerificationController verificationController(AppointmentCargoVerificationService service) {return new AppointmentCargoVerificationController(service);}
@Bean AppointmentSourceCreateController sourceCreateController() { return new AppointmentSourceCreateController(mock(com.baiyue.lastleg.inboundplan.service.InboundFbaAppointmentService.class)); }
@Bean AppointmentCoverageMapper destinationFacts() { return mock(AppointmentCoverageMapper.class); }
/** 权限测试只模拟业务服务,补齐其新增只读端口依赖而不触发数据库。 */
@Bean AppointmentSourceReadRepository sourceReads() { return mock(AppointmentSourceReadRepository.class); }
/** 本测试验证入口权限,不执行货物核实原料读取。 */
@Bean AppointmentCargoReadRepository cargoReads() { return mock(AppointmentCargoReadRepository.class); }
@Bean AppointmentBatchPreviewService batchPreviews() { return mock(AppointmentBatchPreviewService.class); }
@Bean AppointmentBatchPreviewController batchPreviewController(AppointmentBatchPreviewService service) { return new AppointmentBatchPreviewController(service); }
@Bean AppointmentContainerService containers() { return mock(AppointmentContainerService.class); }
@@ -12,56 +12,6 @@ import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class AppointmentSourceServiceTest {
/** 同页共享原料但保留逐组权限、重复和前置错误,结果等于各组独立严格读取。 */
@Test void groupedOrderResolutionSharesReadsWithoutSharingValidationOrFailures() {
var orders=mock(LastlegOrderMapper.class);var shipping=mock(ContainerShippingGateMapper.class);
var external=mock(AppointmentExternalSourceGateway.class);var warehouse=mock(AppointmentWarehouseClient.class);
var service=new AppointmentSourceService(orders,shipping,external,warehouse,"token");
org.springframework.test.util.ReflectionTestUtils.setField(service,"readRepository",new AppointmentSourceReadRepository(orders,shipping,mock(AppointmentCoverageMapper.class)));
var a=new LastlegOrder();a.setId(1L);a.setCustomerId("A");a.setOrderType("FIRST_LEG");a.setSourceContainerBookingId("C1");a.setOverseasWarehouseId("100");a.setDestinationType("FBA");a.setFbaWarehouseCode("ONT8");a.setPackageCount(10);
var b=new LastlegOrder();b.setId(2L);b.setCustomerId("B");b.setOrderType("FIRST_LEG");b.setSourceContainerBookingId("C2");b.setOverseasWarehouseId("100");b.setDestinationType("FBA");b.setFbaWarehouseCode("ONT8");b.setPackageCount(20);
when(orders.selectBatchIds(anyCollection())).thenAnswer(c->List.of(a,b).stream().filter(o->c.<java.util.Collection<Long>>getArgument(0).contains(o.getId())).toList());
var gate=new com.baiyue.lastleg.shipping.entity.ContainerShippingGate();gate.setContainerBookingId("C1");gate.setNodeSequence(40);
when(shipping.selectBatchIds(anyCollection())).thenReturn(List.of(gate));
when(warehouse.catalog("token")).thenReturn(ApiResponse.success(List.of(new AppointmentWarehouseClient.Warehouse(100L,"US-WH","仓","OVERSEAS","US"),new AppointmentWarehouseClient.Warehouse(200L,"ONT8","ONT8","FBA","US"))));
var ref=new AppointmentExternalSourceGateway.OrderRef("A",1L);
var groups=List.of(List.of(ref),List.of(ref),List.of(new AppointmentExternalSourceGateway.OrderRef("B",2L)),List.of(new AppointmentExternalSourceGateway.OrderRef("OTHER",1L)),List.of(ref,ref));
var actual=service.openReadScope().resolveOrderGroups(groups);
assertEquals(actual.get(0).requireSources(),actual.get(1).requireSources());
assertEquals("货物尚无已开船或后续实际船节点",((ResponseStatusException)actual.get(2).error()).getReason());
assertEquals(404,((ResponseStatusException)actual.get(3).error()).getStatusCode().value());
assertEquals(503,((ResponseStatusException)actual.get(4).error()).getStatusCode().value());
assertEquals("来源键必须明确为ORDER或BOOKING",((ResponseStatusException)actual.get(4).error()).getReason());
verify(orders,times(1)).selectBatchIds(anyCollection());verify(shipping,times(1)).selectBatchIds(anyCollection());verify(external,times(1)).resolveOrders(anyList());verify(warehouse,times(1)).catalog("token");
assertEquals(service.resolveBatch(actual.get(0).references()),actual.get(0).requireSources());
assertEquals(400,((ResponseStatusException)service.openReadScope().resolveOrderGroups(List.of(List.of(new AppointmentExternalSourceGateway.OrderRef("x".repeat(129),1L)))).get(0).error()).getStatusCode().value());
var failure=new IllegalStateException("directory unavailable");when(warehouse.catalog("token")).thenThrow(failure);
clearInvocations(warehouse);
var failed=service.openReadScope().resolveOrderGroups(List.of(List.of(ref),List.of(ref)));
assertEquals(503,((ResponseStatusException)failed.get(0).error()).getStatusCode().value());assertSame(failed.get(0).error(),failed.get(1).error());verify(warehouse,times(1)).catalog("token");
service.openReadScope().resolveOrderGroups(List.of(List.of(ref)));verify(warehouse,times(2)).catalog("token");
}
/** 不同订单规范化成同登记仍合一,不同登记重复同实物订单仍拒绝。 */
@Test void groupedCanonicalAliasesKeepOriginalDistinctAndMaterializedRules() {
var orders=mock(LastlegOrderMapper.class);var shipping=mock(ContainerShippingGateMapper.class);var external=mock(AppointmentExternalSourceGateway.class);var warehouse=mock(AppointmentWarehouseClient.class);
var service=new AppointmentSourceService(orders,shipping,external,warehouse,"token");
org.springframework.test.util.ReflectionTestUtils.setField(service,"readRepository",new AppointmentSourceReadRepository(orders,shipping,mock(AppointmentCoverageMapper.class)));
var order=new LastlegOrder();order.setId(1L);order.setCustomerId("A");order.setOverseasWarehouseId("100");order.setDestinationType("FBA");order.setFbaWarehouseCode("ONT8");order.setPackageCount(10);
when(orders.selectBatchIds(anyCollection())).thenReturn(List.of(order));
when(warehouse.catalog("token")).thenReturn(ApiResponse.success(List.of(new AppointmentWarehouseClient.Warehouse(100L,"US-WH","仓","OVERSEAS","US"),new AppointmentWarehouseClient.Warehouse(200L,"ONT8","ONT8","FBA","US"))));
var booking=new AppointmentExternalSourceGateway.Source("A","B1",null,"RECEIVED","100","FBA:ONT8",1L,10,1L);
when(external.resolveOrders(anyList())).thenReturn(List.of(booking,new AppointmentExternalSourceGateway.Source("A","B1",null,"RECEIVED","100","FBA:ONT8",2L,10,1L)));
when(external.resolve(anyList())).thenReturn(List.of(booking));
var input=List.of(List.of(new AppointmentExternalSourceGateway.OrderRef("A",1L),new AppointmentExternalSourceGateway.OrderRef("A",2L)));
assertEquals(1,service.openReadScope().resolveOrderGroups(input).get(0).requireSources().size());
var alias2=new AppointmentExternalSourceGateway.Source("A","B2",null,"RECEIVED","100","FBA:ONT8",2L,10,1L);
when(external.resolveOrders(anyList())).thenReturn(List.of(booking,alias2));
when(external.resolve(anyList())).thenReturn(List.of(booking,new AppointmentExternalSourceGateway.Source("A","B2",null,"RECEIVED","100","FBA:ONT8",1L,10,1L)));
assertEquals("同一货物存在重复来源,请保留原登记来源",((ResponseStatusException)service.openReadScope().resolveOrderGroups(input).get(0).error()).getReason());
when(external.resolveOrders(anyList())).thenReturn(List.of(new AppointmentExternalSourceGateway.Source("A","x".repeat(161),null,"RECEIVED","100","FBA:ONT8",1L,10,1L)));
var invalid=service.openReadScope().resolveOrderGroups(List.of(input.get(0).subList(0,1))).get(0);
assertEquals("请选择完整的真实货源",((ResponseStatusException)invalid.error()).getReason());
}
/** 目录失败不缓存,成功只在当前作用域复用;来源、客户和目的地仍逐次校验。 */
@Test void explicitScopeRetriesFailedCatalogAndNeverCachesSourceOrCrossScopeDirectory() {
var orders=mock(LastlegOrderMapper.class);var shipping=mock(ContainerShippingGateMapper.class);