package com.baiyue.lastleg.pda; import com.baiyue.common.security.AuthenticatedUser; import com.baiyue.lastleg.delivery.controller.OutboundPdaController; import com.baiyue.lastleg.delivery.dto.DeliveryRequests; import com.baiyue.lastleg.delivery.dto.DeliveryResponses; import com.baiyue.lastleg.delivery.service.DeliveryTransferService; import com.baiyue.lastleg.dispatch.pda.controller.DispatchPdaController; import com.baiyue.lastleg.dispatch.pda.dto.DispatchPdaRequests; import com.baiyue.lastleg.dispatch.pda.service.DispatchPdaWorkflowService; import org.junit.jupiter.api.Test; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ResponseStatusException; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; class PdaOutboundDispatchContractTest { private static final String SECURITY = "hasAuthority('lastleg:pda:operate') and hasAuthority('ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR')"; @Test void controllersRequirePdaRoleAndNeverAcceptCustomerOrWarehouseScope() { assertEquals(SECURITY, DispatchPdaController.class.getAnnotation(PreAuthorize.class).value()); assertEquals(SECURITY, OutboundPdaController.class.getAnnotation(PreAuthorize.class).value()); for (Class controller : Set.of(DispatchPdaController.class, OutboundPdaController.class)) { for (Method method : controller.getDeclaredMethods()) { assertTrue(Arrays.stream(method.getParameters()).noneMatch(parameter -> { RequestParam query = parameter.getAnnotation(RequestParam.class); return query != null && Set.of("customerId", "warehouseId") .contains(query.name().isBlank() ? query.value() : query.name()); })); } } assertTrue(Arrays.stream(DispatchPdaRequests.Pick.class.getRecordComponents()) .noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName()))); assertTrue(Arrays.stream(DeliveryRequests.PdaPick.class.getRecordComponents()) .noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName()))); assertTrue(Arrays.stream(DeliveryRequests.PdaPalletPick.class.getRecordComponents()) .noneMatch(component -> "warehouseId".equals(component.getName()) || "customerId".equals(component.getName()))); } @Test void controllersPassOnlyTokenBoundWarehouseToServices() { DispatchPdaWorkflowService dispatchService = mock(DispatchPdaWorkflowService.class); DeliveryTransferService outboundService = mock(DeliveryTransferService.class); DispatchPdaController dispatch = new DispatchPdaController(); OutboundPdaController outbound = new OutboundPdaController(); ReflectionTestUtils.setField(dispatch, "service", dispatchService); ReflectionTestUtils.setField(outbound, "service", outboundService); AuthenticatedUser user = pda(Set.of("lastleg:pda:operate", "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"), 900L); dispatch.list(user, null); outbound.list(user, null); verify(dispatchService).list("900", null); verify(outboundService).pdaTasks("900", null); } @Test void missingRolePermissionOrWarehouseBindingIsRejected() { DispatchPdaController controller = new DispatchPdaController(); assertThrows(ResponseStatusException.class, () -> controller.list(pda(Set.of("lastleg:pda:operate"), 900L), null)); assertThrows(ResponseStatusException.class, () -> controller.list(pda(Set.of("ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"), 900L), null)); assertThrows(ResponseStatusException.class, () -> controller.list(pda(Set.of("lastleg:pda:operate", "ROLE_OVERSEAS_WAREHOUSE_PDA_OPERATOR"), null), null)); } @Test void pdaOpenApiContainsOnlyFrozenDispatchAndOutboundWorkflow() throws Exception { String yaml = Files.readString(Path.of("src/main/resources/openapi/overseas-pda-operations-v1.yaml"), StandardCharsets.UTF_8); assertTrue(yaml.contains("/api/pda/v1/last-leg/dispatches:")); assertTrue(yaml.contains("/api/pda/v1/last-leg/dispatches/{dispatchNo}/depart:")); assertTrue(yaml.contains("/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:")); assertTrue(yaml.contains("/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pallets/{palletNo}/pick:")); assertFalse(yaml.contains("name: customerId")); assertFalse(yaml.contains("name: warehouseId")); assertFalse(yaml.contains("/api/pda/v1/last-leg/delivery")); assertTrue(yaml.contains("inventoryPositionId:")); assertTrue(yaml.contains("cartonMark:")); assertTrue(yaml.contains("availablePackages:")); assertTrue(yaml.contains("pickedPackages:")); assertEquals(Set.of("inventoryPositionId", "palletNo", "cartonMark", "skuCode", "inboundCode", "availablePackages", "pickedPackages"), Arrays.stream(DeliveryResponses.PdaPickPosition.class.getRecordComponents()) .map(component -> component.getName()).collect(java.util.stream.Collectors.toSet())); assertEquals(Set.of("idempotencyKey"), Arrays.stream(DeliveryRequests.PdaComplete.class.getRecordComponents()) .map(component -> component.getName()).collect(java.util.stream.Collectors.toSet())); } private static AuthenticatedUser pda(Set permissions, Long warehouseId) { return new AuthenticatedUser(7L, 0L, 0L, 8L, "picker", "PLATFORM", "PDA", permissions, false, null, null, false, warehouseId); } }