2026-08-19 16:24:02 +08:00
|
|
|
package com.baiyue.lastleg.controller;
|
|
|
|
|
|
2026-08-20 18:02:38 +08:00
|
|
|
import com.baiyue.common.core.api.ApiResponse;
|
|
|
|
|
import com.baiyue.common.core.api.PageResult;
|
2026-08-19 16:24:02 +08:00
|
|
|
import com.baiyue.common.security.AuthenticatedUser;
|
2026-08-25 14:46:06 +08:00
|
|
|
import com.baiyue.common.event.EventEnvelope;
|
|
|
|
|
import com.baiyue.common.event.EventTypes;
|
2026-08-19 16:24:02 +08:00
|
|
|
import com.baiyue.lastleg.dto.LastlegOrderRequests;
|
2026-08-20 18:02:38 +08:00
|
|
|
import com.baiyue.lastleg.dto.LastlegOrderSummary;
|
2026-08-19 16:24:02 +08:00
|
|
|
import com.baiyue.lastleg.security.InternalServiceTokenGuard;
|
|
|
|
|
import com.baiyue.lastleg.service.LastlegOrderService;
|
2026-09-07 23:58:18 +08:00
|
|
|
import com.baiyue.lastleg.service.LastlegOrderCommandService;
|
|
|
|
|
import com.baiyue.lastleg.dto.LastlegOrderCommandDtos;
|
2026-08-19 16:24:02 +08:00
|
|
|
import com.baiyue.lastleg.service.LoadingCompletedConsumer;
|
2026-08-25 10:31:04 +08:00
|
|
|
import com.baiyue.lastleg.shipping.service.ContainerShippingNodeConsumer;
|
2026-08-19 16:24:02 +08:00
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
import org.springframework.test.util.ReflectionTestUtils;
|
2026-09-06 16:02:11 +08:00
|
|
|
import org.springframework.beans.BeanUtils;
|
2026-08-21 10:43:25 +08:00
|
|
|
import org.springframework.web.server.ResponseStatusException;
|
2026-08-19 16:24:02 +08:00
|
|
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
2026-08-20 18:02:38 +08:00
|
|
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
2026-08-31 14:39:40 +08:00
|
|
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
2026-08-21 10:43:25 +08:00
|
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
2026-08-19 16:24:02 +08:00
|
|
|
import static org.mockito.ArgumentMatchers.eq;
|
2026-08-21 10:43:25 +08:00
|
|
|
import static org.mockito.ArgumentMatchers.isNull;
|
2026-08-19 16:24:02 +08:00
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
|
|
import static org.mockito.Mockito.verify;
|
2026-08-25 14:46:06 +08:00
|
|
|
import static org.mockito.Mockito.never;
|
|
|
|
|
import static org.mockito.Mockito.when;
|
2026-08-19 16:24:02 +08:00
|
|
|
|
|
|
|
|
class LastlegOrderControllerTest {
|
2026-08-28 13:54:32 +08:00
|
|
|
// Controller contract tests intentionally instantiate the controller without a Spring context here.
|
2026-08-19 16:24:02 +08:00
|
|
|
private final LastlegOrderService service = mock(LastlegOrderService.class);
|
2026-09-07 23:58:18 +08:00
|
|
|
private final LastlegOrderCommandService commandService = mock(LastlegOrderCommandService.class);
|
2026-08-19 16:24:02 +08:00
|
|
|
private final LastlegOrderController controller = controller();
|
|
|
|
|
private final LastlegOrderRequests.Query query = new LastlegOrderRequests.Query(
|
2026-09-06 16:02:11 +08:00
|
|
|
1L, 20L, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void orderQueryHasOneResolvableConstructorForRequestParameterBinding() {
|
|
|
|
|
assertEquals(16, BeanUtils.getResolvableConstructor(LastlegOrderRequests.Query.class).getParameterCount());
|
|
|
|
|
}
|
2026-08-19 16:24:02 +08:00
|
|
|
|
|
|
|
|
@Test
|
2026-08-21 10:43:25 +08:00
|
|
|
void platformEndpointRejectsMerchantIdentity() {
|
|
|
|
|
assertThrows(ResponseStatusException.class,
|
|
|
|
|
() -> controller.list(user(100L, "MERCHANT"), "200", query));
|
2026-08-19 16:24:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
2026-08-21 10:43:25 +08:00
|
|
|
void platformOperatorCanQueryAllCustomerTenantsWithoutCustomerId() {
|
|
|
|
|
controller.list(user(0L, "PLATFORM"), null, query);
|
|
|
|
|
verify(service).page(isNull(), eq(query));
|
2026-08-19 16:24:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void platformOperatorCanQueryExplicitCustomerTenant() {
|
|
|
|
|
controller.list(user(0L, "PLATFORM"), "200", query);
|
|
|
|
|
verify(service).page(eq("200"), eq(query));
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-07 23:58:18 +08:00
|
|
|
@Test
|
|
|
|
|
void holdCommandPassesVersionedIdempotentRequestToSingleOrderService() {
|
|
|
|
|
var request = new LastlegOrderCommandDtos.HoldRequest(true, true, "海关查验", 2);
|
|
|
|
|
|
|
|
|
|
controller.hold(user(0L, "PLATFORM"), "100", 11L, "hold-11", request);
|
|
|
|
|
|
|
|
|
|
verify(commandService).hold("100", 11L, "hold-11", request, "1", "tester");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void manualNodeQueryUsesOrderScopeRatherThanContainerScope() {
|
|
|
|
|
controller.manualNodes(user(0L, "PLATFORM"), "100", 11L);
|
|
|
|
|
|
|
|
|
|
verify(commandService).manualNodes("100", 11L);
|
|
|
|
|
verify(service, never()).containerProgressDetail(org.mockito.ArgumentMatchers.anyString());
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-27 17:16:33 +08:00
|
|
|
@Test
|
|
|
|
|
void platformOperatorCanQueryCrossCustomerContainerSummaries() {
|
2026-08-28 13:54:32 +08:00
|
|
|
var summaryQuery = new LastlegOrderRequests.ContainerProgressQuery(1, 20, "CONT-", null, null);
|
2026-08-27 17:16:33 +08:00
|
|
|
|
|
|
|
|
controller.containerSummaries(user(0L, "PLATFORM"), summaryQuery);
|
|
|
|
|
|
2026-08-28 13:54:32 +08:00
|
|
|
verify(service).containerProgressPage(summaryQuery);
|
2026-08-27 17:16:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void containerSummariesRejectMerchantIdentity() {
|
2026-08-28 13:54:32 +08:00
|
|
|
var summaryQuery = new LastlegOrderRequests.ContainerProgressQuery(1, 20, null, null, null);
|
2026-08-27 17:16:33 +08:00
|
|
|
|
|
|
|
|
assertThrows(ResponseStatusException.class,
|
|
|
|
|
() -> controller.containerSummaries(user(100L, "MERCHANT"), summaryQuery));
|
2026-08-28 13:54:32 +08:00
|
|
|
verify(service, never()).containerProgressPage(summaryQuery);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void platformOperatorCanQueryUnpagedContainerDetailByStableId() {
|
|
|
|
|
controller.containerSummaryDetail(user(0L, "PLATFORM"), "BOOKING-1");
|
|
|
|
|
|
|
|
|
|
verify(service).containerProgressDetail("BOOKING-1");
|
2026-08-27 17:16:33 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-31 14:39:40 +08:00
|
|
|
@Test
|
|
|
|
|
void exportBuildsCsvBeforeReturningTheResponse() {
|
|
|
|
|
byte[] csv = "csv".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
|
|
|
when(service.exportCsv("200", query)).thenReturn(csv);
|
|
|
|
|
|
|
|
|
|
var response = controller.export(user(0L, "PLATFORM"), "200", query);
|
|
|
|
|
|
|
|
|
|
assertArrayEquals(csv, response.getBody());
|
|
|
|
|
verify(service).exportCsv("200", query);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void platformOperatorCanExportAllCustomersWithoutFilters() {
|
|
|
|
|
byte[] csv = "all customers".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
|
|
|
|
LastlegOrderRequests.Query emptyQuery = new LastlegOrderRequests.Query(
|
2026-09-06 16:02:11 +08:00
|
|
|
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
2026-08-31 14:39:40 +08:00
|
|
|
when(service.exportCsv(null, emptyQuery)).thenReturn(csv);
|
|
|
|
|
|
|
|
|
|
var response = controller.export(user(0L, "PLATFORM"), null, emptyQuery);
|
|
|
|
|
|
|
|
|
|
assertEquals(Long.valueOf(1), emptyQuery.page());
|
|
|
|
|
assertEquals(Long.valueOf(20), emptyQuery.size());
|
|
|
|
|
assertArrayEquals(csv, response.getBody());
|
|
|
|
|
verify(service).exportCsv(isNull(), eq(emptyQuery));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-25 14:46:06 +08:00
|
|
|
@Test
|
|
|
|
|
void iamSecurityEventsAreRejectedBeforeLoadingCompletedConsumer() {
|
|
|
|
|
EventEnvelope event = mock(EventEnvelope.class);
|
|
|
|
|
when(event.eventType()).thenReturn(EventTypes.IAM_USER_STATUS_CHANGED);
|
|
|
|
|
LoadingCompletedConsumer loading = (LoadingCompletedConsumer) ReflectionTestUtils.getField(controller, "consumer");
|
|
|
|
|
|
|
|
|
|
assertThrows(ResponseStatusException.class, () -> controller.receive("internal-token", event));
|
|
|
|
|
|
|
|
|
|
verify(loading, never()).consume(event);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-19 16:24:02 +08:00
|
|
|
private static AuthenticatedUser user(long customerId, String appCode) {
|
|
|
|
|
return new AuthenticatedUser(1L, customerId, customerId, 1L, "tester", appCode, "WEB", Set.of());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private LastlegOrderController controller() {
|
|
|
|
|
LastlegOrderController controller = new LastlegOrderController();
|
|
|
|
|
ReflectionTestUtils.setField(controller, "consumer", mock(LoadingCompletedConsumer.class));
|
2026-08-25 10:31:04 +08:00
|
|
|
ReflectionTestUtils.setField(controller, "shippingNodeConsumer", mock(ContainerShippingNodeConsumer.class));
|
2026-08-19 16:24:02 +08:00
|
|
|
ReflectionTestUtils.setField(controller, "orderService", service);
|
2026-09-07 23:58:18 +08:00
|
|
|
ReflectionTestUtils.setField(controller, "commandService", commandService);
|
2026-08-19 16:24:02 +08:00
|
|
|
ReflectionTestUtils.setField(controller, "tokenGuard", mock(InternalServiceTokenGuard.class));
|
|
|
|
|
return controller;
|
|
|
|
|
}
|
|
|
|
|
}
|