fix(firstleg): validate public port schedule queries with zero scope

This commit is contained in:
2026-10-02 11:16:15 +08:00
parent 8b9b135ab5
commit 0b34f99be5
2 changed files with 41 additions and 1 deletions
@@ -16,7 +16,7 @@ import java.util.List;
public final class PortScheduleDtos {
private PortScheduleDtos() {}
public record Query(@Positive Long customerId, @Positive Long oceanBookingId,
public record Query(@Min(0) Long customerId, @Positive Long oceanBookingId,
@Positive Long containerBookingId, Boolean expired,
@Size(max = 128) String keyword, @Min(1) long page,
@Min(1) @Max(200) long size) {
@@ -15,6 +15,7 @@ import com.baiyue.firstleg.portschedule.repository.PortScheduleRepository;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import jakarta.validation.Validation;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@@ -34,6 +35,16 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class PortScheduleServiceTest {
@Test
void platformPublicQueryAllowsZeroButRejectsNegativeCustomerScope() {
try (var factory = Validation.buildDefaultValidatorFactory()) {
var validator = factory.getValidator();
assertThat(validator.validate(new com.baiyue.firstleg.portschedule.dto.PortScheduleDtos.Query(
0L, null, null, null, null, 1, 5))).isEmpty();
assertThat(validator.validate(new com.baiyue.firstleg.portschedule.dto.PortScheduleDtos.Query(
-1L, null, null, null, null, 1, 5))).anyMatch(row -> row.getPropertyPath().toString().equals("customerId"));
}
}
@Mock private PortScheduleRepository repository;
@InjectMocks private PortScheduleService service;
@@ -79,6 +90,35 @@ class PortScheduleServiceTest {
verify(repository, never()).insert(any(PortScheduleEntity.class));
}
@Test
void platformBookingRequiresZeroScopeAndRejectsArbitraryCustomerWithoutInsert() {
OceanBookingEntity platformBooking = new OceanBookingEntity();
platformBooking.setId(10L);
when(repository.oceanBooking(10L)).thenReturn(platformBooking);
assertThatThrownBy(() -> service.create(7L, "admin", "wrong-platform-scope",
new CreateRequest(0, data(null, future(1), future(2)), null)))
.isInstanceOf(BizException.class).hasMessageContaining("customerId与订舱客户不一致");
verify(repository, never()).insert(any(PortScheduleEntity.class));
}
@Test
void createsZeroScopeScheduleForPlatformBooking() {
OceanBookingEntity platformBooking = new OceanBookingEntity();
platformBooking.setId(10L);
when(repository.oceanBooking(10L)).thenReturn(platformBooking);
when(repository.containerBooking(20L)).thenReturn(new ContainerBookingEntity());
when(repository.allocation(10L, 20L)).thenReturn(new OceanBookingContainerAllocationEntity());
ScheduleData d = data(20L, future(1), future(2));
ScheduleData platformData = new ScheduleData(0L, d.oceanBookingId(), d.containerBookingId(),
d.sourceType(), d.sourceName(), d.sourceRef(), d.vesselName(), d.voyageNo(),
d.departurePortCode(), d.departurePortName(), d.destinationPortCode(), d.destinationPortName(),
d.etdAt(), d.etaAt(), d.bookingCutoffAt(), d.customsCutoffAt(), d.portCutoffAt(),
d.observedAt(), d.expiresAt(), d.remark());
Detail result = service.create(7L, "admin", "platform-scope-create",
new CreateRequest(0, platformData, "平台公共船期"));
assertThat(result.portSchedule().schedule().getCustomerId()).isZero();
}
@Test
void rejectsStaleUpdateWithoutMutation() {
when(repository.find(1L)).thenReturn(schedule(3));