fix(shipping): allow platform scope tracking resources

This commit is contained in:
2026-09-30 13:03:33 +08:00
parent d320e78bbc
commit eae630e60d
2 changed files with 126 additions and 6 deletions
@@ -66,7 +66,6 @@ public class PlatformCarrierScheduleQueryController {
public ApiResponse<PageResult<QueryView>> 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> 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<List<History>> 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<Detail> 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));
}
}
@@ -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<QueryView>(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<Runnable> 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())