From eae630e60df1ddfd8076f2330753cd3ebb3ff70c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 30 Sep 2026 13:03:33 +0800 Subject: [PATCH] fix(shipping): allow platform scope tracking resources --- ...latformCarrierScheduleQueryController.java | 11 +- ...erScheduleQueryControllerContractTest.java | 121 ++++++++++++++++++ 2 files changed, 126 insertions(+), 6 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryController.java b/firstleg/src/main/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryController.java index 9a17404d7..1b84a2cab 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryController.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryController.java @@ -66,7 +66,6 @@ public class PlatformCarrierScheduleQueryController { public ApiResponse> page(@AuthenticationPrincipal AuthenticatedUser user, @Valid Query query) { FirstlegScopeGuard.platform(user); - if (query.customerId() != null) FirstlegScopeGuard.platform(user, query.customerId()); return ApiResponse.success(service.page(query)); } @@ -74,7 +73,7 @@ public class PlatformCarrierScheduleQueryController { @PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:list')") public ApiResponse detail(@AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long queryId) { - FirstlegScopeGuard.platform(user, service.customerId(queryId)); + FirstlegScopeGuard.platform(user); return ApiResponse.success(service.detail(queryId)); } @@ -82,7 +81,7 @@ public class PlatformCarrierScheduleQueryController { @PreAuthorize("hasAuthority('platform:firstleg:port-schedule-query:list')") public ApiResponse> history( @AuthenticationPrincipal AuthenticatedUser user, @PathVariable @Positive long queryId) { - FirstlegScopeGuard.platform(user, service.customerId(queryId)); + FirstlegScopeGuard.platform(user); return ApiResponse.success(service.history(queryId)); } @@ -93,7 +92,7 @@ public class PlatformCarrierScheduleQueryController { public ApiResponse create(@AuthenticationPrincipal AuthenticatedUser user, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody CreateRequest request) { - FirstlegScopeGuard.platform(user, request.customerId()); + FirstlegScopeGuard.platform(user); return ApiResponse.success(service.create(user.userId(), user.username(), key, request)); } @@ -105,7 +104,7 @@ public class PlatformCarrierScheduleQueryController { @PathVariable @Positive long queryId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ManualResultRequest request) { - FirstlegScopeGuard.platform(user, service.customerId(queryId)); + FirstlegScopeGuard.platform(user); return ApiResponse.success(service.attachManualResult(queryId, user.userId(), user.username(), key, request)); } @@ -118,7 +117,7 @@ public class PlatformCarrierScheduleQueryController { @PathVariable @Positive long queryId, @RequestHeader("Idempotency-Key") String key, @Valid @RequestBody ManualNodeRequest request) { - FirstlegScopeGuard.platform(user, service.customerId(queryId)); + FirstlegScopeGuard.platform(user); return ApiResponse.success(service.addManualNode(queryId, user.userId(), user.username(), key, request)); } } diff --git a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryControllerContractTest.java index 64933838e..59a830387 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/portschedule/controller/PlatformCarrierScheduleQueryControllerContractTest.java @@ -1,17 +1,138 @@ package com.baiyue.firstleg.portschedule.controller; import com.baiyue.common.audit.BusinessAudit; +import com.baiyue.common.core.api.PageResult; +import com.baiyue.common.core.api.ResultCode; +import com.baiyue.common.core.exception.BizException; +import com.baiyue.common.security.AuthenticatedUser; +import com.baiyue.firstleg.portschedule.dto.CarrierScheduleQueryDtos.*; +import com.baiyue.firstleg.portschedule.service.CarrierScheduleQueryService; +import jakarta.validation.Validation; +import org.junit.jupiter.api.BeforeEach; 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.RequestMapping; import java.lang.reflect.Method; +import java.time.LocalDateTime; import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; class PlatformCarrierScheduleQueryControllerContractTest { + private final CarrierScheduleQueryService service = mock(CarrierScheduleQueryService.class); + private final PlatformCarrierScheduleQueryController controller = new PlatformCarrierScheduleQueryController(); + private final AuthenticatedUser platform = user("PLATFORM", 0L); + private final long queryId = 88L; + private final Query query = new Query(0L, 12L, 13L, null, null, null, null, null, null); + private final CreateRequest create = new CreateRequest(0, 0L, 12L, 13L, "COSCO", "CONTAINER", "TEST1234567", null); + private final ManualResultRequest result = new ManualResultRequest(1, 99L, "人工关联船期"); + private final ManualNodeRequest node = new ManualNodeRequest(1, "DEPARTED_ORIGIN_PORT", + LocalDateTime.of(2026, 9, 30, 10, 0), null, LocalDateTime.of(2026, 9, 30, 10, 0), + "CNSHA", "上海港", "人工确认实际开航"); + private Detail detail; + + @BeforeEach + void setUp() { + ReflectionTestUtils.setField(controller, "service", service); + Task task = mock(Task.class); + when(task.customerId()).thenReturn(0L); + detail = new Detail(new QueryView(task, false), List.of(), List.of(), List.of()); + // Platform bookings persist task ownership as zero. The former positive-target guard rejected it. + when(service.customerId(anyLong())).thenReturn(0L); + } + + @Test + void platformCanFilterZeroScopeTasks() { + var page = new PageResult(List.of(detail.query()), 1, 20, 1, 1); + when(service.page(query)).thenReturn(page); + assertThat(controller.page(platform, query).data()).isSameAs(page); + verify(service).page(query); + } + + @Test + void platformCanReadZeroScopeTaskDetail() { + when(service.detail(queryId)).thenReturn(detail); + assertThat(controller.detail(platform, queryId).data()).isSameAs(detail); + assertThat(detail.query().query().customerId()).isZero(); + verify(service).detail(queryId); + } + + @Test + void platformCanReadZeroScopeTaskHistory() { + var history = List.of(new History(5L, "OUTBOUND_CONFIRM", 0, 1, "自动离仓", + 7L, "SYSTEM", LocalDateTime.of(2026, 9, 30, 10, 0))); + when(service.history(queryId)).thenReturn(history); + assertThat(controller.history(platform, queryId).data()).isSameAs(history); + verify(service).history(queryId); + } + + @Test + void platformCanCreateZeroScopeTaskThroughNormalLinkValidation() { + when(service.create(7L, "operator", "create-key", create)).thenReturn(detail); + assertThat(controller.create(platform, "create-key", create).data()).isSameAs(detail); + verify(service).create(7L, "operator", "create-key", create); + } + + @Test + void platformCanAttachZeroScopeTaskManualResult() { + when(service.attachManualResult(queryId, 7L, "operator", "result-key", result)).thenReturn(detail); + assertThat(controller.manualResult(platform, queryId, "result-key", result).data()).isSameAs(detail); + verify(service).attachManualResult(queryId, 7L, "operator", "result-key", result); + } + + @Test + void platformCanAppendZeroScopeTaskManualNode() { + when(service.addManualNode(queryId, 7L, "operator", "node-key", node)).thenReturn(detail); + assertThat(controller.manualNode(platform, queryId, "node-key", node).data()).isSameAs(detail); + verify(service).addManualNode(queryId, 7L, "operator", "node-key", node); + } + + @Test + void nonPlatformPrincipalsAreRejectedBeforeAnyResourceLookupOrMutation() { + for (AuthenticatedUser denied : Arrays.asList(null, user("MERCHANT", 42L), user("PDA", 0L), + user("PLATFORM", 42L), user("PLATFORM", null))) { + List actions = List.of( + () -> controller.carriers(denied), + () -> controller.containerActualFacts(denied, List.of(13L)), + () -> controller.page(denied, query), + () -> controller.detail(denied, queryId), + () -> controller.history(denied, queryId), + () -> controller.create(denied, "create-key", create), + () -> controller.manualResult(denied, queryId, "result-key", result), + () -> controller.manualNode(denied, queryId, "node-key", node)); + for (Runnable action : actions) { + assertThatThrownBy(action::run).isInstanceOfSatisfying(BizException.class, + failure -> assertThat(failure.getResultCode()).isEqualTo(ResultCode.FORBIDDEN)); + } + } + verifyNoInteractions(service); + } + + @Test + void requestValidationAllowsPlatformZeroAndRejectsNegativeScope() { + try (var factory = Validation.buildDefaultValidatorFactory()) { + var validator = factory.getValidator(); + assertThat(validator.validate(query)).isEmpty(); + assertThat(validator.validate(create)).isEmpty(); + assertThat(validator.validate(new Query(-1L, 12L, 13L, null, null, null, null, null, null))) + .anyMatch(violation -> violation.getPropertyPath().toString().equals("customerId")); + assertThat(validator.validate(new CreateRequest(0, -1L, 12L, 13L, "COSCO", "CONTAINER", "TEST1234567", null))) + .anyMatch(violation -> violation.getPropertyPath().toString().equals("customerId")); + } + } + + private static AuthenticatedUser user(String appCode, Long scope) { + return new AuthenticatedUser(7L, scope, 0L, null, "operator", appCode, "WEB", Set.of()); + } + @Test void exposesDedicatedTaskResourceAndLeastPrivilegeActions() { assertThat(PlatformCarrierScheduleQueryController.class.getAnnotation(RequestMapping.class).value())