This commit is contained in:
2026-10-08 17:07:57 +08:00
parent 16d2d9111a
commit c1ddb658bd
111 changed files with 2914 additions and 941 deletions
@@ -0,0 +1,101 @@
package com.baiyue.lastleg.export;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.export.ExportContracts.ExportBatchRequest;
import com.baiyue.lastleg.dto.LastlegOrderRequests;
import com.baiyue.lastleg.service.LastlegOrderService;
import com.baiyue.lastleg.inventory.query.dto.InventoryQueryRequests;
import com.baiyue.lastleg.inventory.query.service.InventoryQueryService;
import com.baiyue.lastleg.masterdata.exchange.dto.MonthlyExchangeRateDtos;
import com.baiyue.lastleg.masterdata.exchange.service.MonthlyExchangeRateService;
import com.baiyue.lastleg.fleet.reporting.TripOperationsModels;
import com.baiyue.lastleg.fleet.reporting.TripOperationsService;
import com.baiyue.lastleg.security.InternalServiceTokenGuard;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import jakarta.validation.Validation;
import org.junit.jupiter.api.*;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class LastlegExportProviderServiceTest {
private LastlegExportProviderService service;
private LastlegOrderService orders;
private InventoryQueryService inventory;
private MonthlyExchangeRateService rates;
private TripOperationsService operations;
private jakarta.validation.ValidatorFactory validators;
@BeforeEach void setup() {
service = new LastlegExportProviderService();
orders = mock(LastlegOrderService.class); inventory = mock(InventoryQueryService.class);
rates = mock(MonthlyExchangeRateService.class); operations = mock(TripOperationsService.class);
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "inventory", inventory);
ReflectionTestUtils.setField(service, "rates", rates);
ReflectionTestUtils.setField(service, "operations", operations);
ReflectionTestUtils.setField(service, "json", new ObjectMapper().findAndRegisterModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS));
validators = Validation.buildDefaultValidatorFactory();
ReflectionTestUtils.setField(service, "validator", validators.getValidator());
ReflectionTestUtils.setField(service, "signingKey", "test-internal-token-with-at-least-32-bytes");
}
@AfterEach void close() { validators.close(); }
@Test void batchesAllRowsAndBindsSnapshotToFiltersRequesterAndAttempt() {
var row = new MonthlyExchangeRateDtos.Response("2026-10", "USD", "CNY", new BigDecimal("7.12"), "ACTIVE", 1, "operator", LocalDateTime.of(2026,10,8,12,0), null);
when(rates.page(any())).thenAnswer(call -> {
MonthlyExchangeRateDtos.Query q = call.getArgument(0);
return new PageResult<>(List.of(row), q.page(), q.size(), 2, 2);
});
var first = service.batch("MONTHLY_EXCHANGE_RATE_LIST", request(Map.of("status", "ACTIVE"), null, null));
assertTrue(first.hasMore()); assertEquals(2L, first.totalRows());
assertEquals(new BigDecimal("7.12"), first.rows().get(0).get("usdToCnyAverage"));
assertEquals("2026-10-08T12:00:00", first.rows().get(0).get("updatedAt"));
var second = service.batch("MONTHLY_EXCHANGE_RATE_LIST", request(Map.of("status", "ACTIVE"), first.nextCursor(), first.snapshotToken()));
assertFalse(second.hasMore()); assertTrue(second.columns().isEmpty()); assertEquals(first.schemaHash(), second.schemaHash());
verify(rates).page(new MonthlyExchangeRateDtos.Query(2,1,null,null,"ACTIVE"));
assertThrows(ResponseStatusException.class, () -> service.batch("MONTHLY_EXCHANGE_RATE_LIST", request(Map.of("status", "INACTIVE"), "1", first.snapshotToken())));
assertThrows(ResponseStatusException.class, () -> service.batch("MONTHLY_EXCHANGE_RATE_LIST", new ExportBatchRequest("t","0","PLATFORM","other",1,Map.of("status","ACTIVE"),"1",first.snapshotToken(),1)));
assertThrows(ResponseStatusException.class, () -> service.batch("MONTHLY_EXCHANGE_RATE_LIST", new ExportBatchRequest("t","0","PLATFORM","u",2,Map.of("status","ACTIVE"),"1",first.snapshotToken(),1)));
}
@Test void retainsOrderStageFalseAndRelabelFiltersWithoutExportingInternalIds() {
var order = new ObjectMapper().findAndRegisterModules().convertValue(Map.of("id", "9007199254740993", "orderNo", "LL-1", "currentStatus", "PENDING_ASSIGNMENT", "abnormal", false, "detained", true), com.baiyue.lastleg.dto.LastlegOrderSummary.class);
when(orders.page(isNull(), any(), eq("RL-1"))).thenReturn(new PageResult<>(List.of(order),1,1,1,1));
var result = service.batch("LAST_MILE_ORDER_LIST", request(Map.of("detained",false,"sailingStage","BEFORE_DEPARTURE","relabelNo","RL-1"),null,null));
verify(orders).page(isNull(), argThat(q -> Boolean.FALSE.equals(q.detained()) && q.sailingStage() == LastlegOrderRequests.SailingStage.BEFORE_DEPARTURE), eq("RL-1"));
assertEquals("否", result.rows().get(0).get("abnormal"));
assertEquals("是", result.rows().get(0).get("detained"));
assertEquals("待分配", result.rows().get(0).get("currentStatus"));
assertTrue(result.columns().stream().noneMatch(c -> c.key().equals("id") || c.key().equals("customerId")));
}
@Test void keepsCustomerScopeAndLedgerCustomerFilter() {
when(inventory.ledgers(eq("200"), any())).thenReturn(new PageResult<>(List.of(),1,1,0,0));
service.batch("OVERSEAS_INVENTORY_LEDGER", request(Map.of("customerId","200","customerName","客户"),null,null));
verify(inventory).ledgers(eq("200"), argThat(q -> "客户".equals(q.customerName())));
}
@Test void defaultsCostVisibilityClosed() {
when(operations.report(any(), eq(false))).thenReturn(new TripOperationsModels.Result(new PageResult<>(List.of(),1,1,0,0),null,null));
service.batch("TRIP_OPERATIONS_LIST",request(Map.of("from","2026-10-01T00:00:00Z","to","2026-10-08T00:00:00Z"),null,null));
verify(operations).report(any(), eq(false));
}
@Test void rejectsScopeInvalidParametersAndUntrustedInternalCalls() {
assertThrows(ResponseStatusException.class, () -> service.batch("LAST_MILE_ORDER_LIST",new ExportBatchRequest("t","1","MERCHANT","u",1,Map.of(),null,null,1)));
assertThrows(ResponseStatusException.class, () -> service.batch("LAST_MILE_ORDER_LIST",request(Map.of("unknown","x"),null,null)));
assertThrows(ResponseStatusException.class, () -> service.batch("LAST_MILE_ORDER_LIST",request(Map.of(),"1",null)));
assertThrows(ResponseStatusException.class, () -> new LastlegExportProviderController(new InternalServiceTokenGuard("expected"),service).batch("wrong","LAST_MILE_ORDER_LIST",request(Map.of(),null,null)));
verifyNoInteractions(orders);
}
private ExportBatchRequest request(Map<String,Object> p,String cursor,String snapshot) {
return new ExportBatchRequest("t","0","PLATFORM","u",1,p,cursor,snapshot,1);
}
}