暂存提交

This commit is contained in:
2026-08-19 16:24:02 +08:00
parent f5c4bf130a
commit 6acb6178da
354 changed files with 25000 additions and 198 deletions
@@ -0,0 +1,25 @@
package com.baiyue.lastleg.trucking;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UsTruckOrderSchemaContractTest {
@Test
void baselineContainsOrderAndImmutableHistoryTables() throws Exception {
String sql;
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
sql = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order"));
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_status_history"));
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_us_truck_order_pallet"));
assertTrue(sql.contains("pallet_count INT NOT NULL DEFAULT 1"));
assertTrue(sql.contains("received_pallet_count INT NOT NULL DEFAULT 0"));
assertTrue(sql.contains("receipt_idempotency_key VARCHAR(255) NULL"));
assertTrue(sql.contains("UNIQUE KEY uk_ll_us_truck_order_key(customer_id,idempotency_key)"));
assertTrue(sql.contains("KEY idx_ll_us_truck_history(customer_id,order_id,created_at)"));
}
}
@@ -0,0 +1,57 @@
package com.baiyue.lastleg.trucking.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests;
import com.baiyue.lastleg.trucking.service.UsTruckOrderService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class UsTruckOrderControllerTest {
@Mock UsTruckOrderService service;
private MerchantUsTruckOrderController merchant;
private PlatformUsTruckOrderController platform;
@BeforeEach
void setUp() {
merchant = new MerchantUsTruckOrderController(); platform = new PlatformUsTruckOrderController();
ReflectionTestUtils.setField(merchant, "service", service); ReflectionTestUtils.setField(platform, "service", service);
}
@Test
void merchantListAlwaysUsesTrustedTenant() {
var query = query(); merchant.page(user(100L, "MERCHANT", Set.of()), query);
verify(service).page("100", query);
}
@Test
void platformStatusChangeRequiresManagePermission() {
var request = new UsTruckOrderRequests.ChangeStatus(0L, "DISPATCHED", null);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> platform.changeStatus(user(0L, "PLATFORM", Set.of()), "100", "TK1", request));
assertEquals(403, error.getStatusCode().value());
}
@Test
void platformStatusChangeUsesExplicitTargetTenant() {
var request = new UsTruckOrderRequests.ChangeStatus(0L, "DISPATCHED", "truck assigned");
platform.changeStatus(user(0L, "PLATFORM", Set.of(UsTruckOrderService.MANAGE_PERMISSION)), "100", "TK1", request);
verify(service).changeStatus("100", "TK1", "7", request);
}
private UsTruckOrderRequests.Query query() { return new UsTruckOrderRequests.Query(1, 20, null, null, null, null, null, null, null); }
private AuthenticatedUser user(long tenant, String app, Set<String> permissions) {
return new AuthenticatedUser(7L, tenant, tenant, 8L, "operator", app, "WEB", permissions);
}
}
@@ -0,0 +1,142 @@
package com.baiyue.lastleg.trucking.service;
import com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests;
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
import com.baiyue.lastleg.trucking.entity.UsTruckOrderPallet;
import com.baiyue.lastleg.trucking.entity.UsTruckOrderStatusHistory;
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderMapper;
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderPalletMapper;
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderStatusHistoryMapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.server.ResponseStatusException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class UsTruckOrderServiceTest {
@Mock UsTruckOrderMapper orders;
@Mock UsTruckOrderPalletMapper pallets;
@Mock UsTruckOrderStatusHistoryMapper histories;
private UsTruckOrderService service;
@BeforeEach
void setUp() {
service = new UsTruckOrderService();
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "pallets", pallets);
ReflectionTestUtils.setField(service, "histories", histories);
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules());
org.mockito.Mockito.lenient().when(pallets.listOrder(anyString(), any())).thenReturn(List.of());
}
@Test
void customerOrderStartsPendingConfirmationAndUsesUsdByDefault() {
AtomicReference<UsTruckOrder> saved = new AtomicReference<>();
when(orders.insert(any(UsTruckOrder.class))).thenAnswer(call -> { saved.set(call.getArgument(0)); return 1; });
when(orders.findNo(anyString(), anyString())).thenAnswer(call -> saved.get());
when(histories.listOrder(anyString(), any())).thenReturn(List.of());
var detail = service.create("100", "7", create("key-1"));
assertEquals(UsTruckOrderService.PENDING_CONFIRMATION, detail.summary().status());
assertEquals("USD", detail.summary().currency());
assertEquals("100", detail.summary().customerId());
}
@Test
void statusChangePersistsHistoryAndIncrementsVersion() {
UsTruckOrder order = order(UsTruckOrderService.PENDING_CONFIRMATION, 0L);
when(orders.lockNo("100", "TK1")).thenReturn(order);
when(orders.findNo("100", "TK1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
var result = service.changeStatus("100", "TK1", "7",
new UsTruckOrderRequests.ChangeStatus(0L, "WAITING_INBOUND", "accepted"));
assertEquals(UsTruckOrderService.WAITING_INBOUND, result.summary().status());
assertEquals(1, result.summary().version());
verify(histories).insert(any(UsTruckOrderStatusHistory.class));
}
@Test
void staleStatusChangeIsRejected() {
when(orders.lockNo("100", "TK1")).thenReturn(order(UsTruckOrderService.DISPATCHED, 2L));
ResponseStatusException error = assertThrows(ResponseStatusException.class, () ->
service.changeStatus("100", "TK1", "7", new UsTruckOrderRequests.ChangeStatus(1L, "IN_TRANSIT", null)));
assertEquals(409, error.getStatusCode().value());
}
@Test
void detailIncludesBoundPallets() {
UsTruckOrder order = order(UsTruckOrderService.WAITING_INBOUND, 1L);
UsTruckOrderPallet pallet = new UsTruckOrderPallet();
pallet.setId(10L); pallet.setPalletNo("PLT-001"); pallet.setSequenceNo(1);
pallet.setSource("SYSTEM"); pallet.setStatus("OCCUPIED");
pallet.setWarehousePalletId(20L); pallet.setPackageCount(12);
when(orders.findNo("100", "TK1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
when(pallets.listOrder("100", 1L)).thenReturn(List.of(pallet));
var detail = service.detail("100", "TK1");
assertEquals(1, detail.pallets().size());
assertEquals("PLT-001", detail.pallets().get(0).palletNo());
assertEquals(12, detail.pallets().get(0).packageCount());
}
@Test
void receivingMorePalletsCreatesOverageAndKeepsDeclaredCount() {
UsTruckOrder order = order(UsTruckOrderService.WAITING_INBOUND, 0L);
order.setPalletCount(1);
when(orders.lockNo("100", "TK1")).thenReturn(order);
when(orders.findNo("100", "TK1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
List<UsTruckOrderPallet> savedPallets = new ArrayList<>();
when(pallets.listOrder("100", 1L)).thenAnswer(call -> List.copyOf(savedPallets));
when(pallets.insert(any(UsTruckOrderPallet.class))).thenAnswer(call -> {
savedPallets.add(call.getArgument(0));
return 1;
});
var result = service.receivePallets("100", "TK1", "7",
new UsTruckOrderRequests.ReceivePallets(0L, 3, 0, "receive-1", "actual three pallets"));
assertEquals(1, result.summary().palletCount());
assertEquals(3, result.summary().receivedPalletCount());
assertEquals(UsTruckOrderService.PALLET_OVERAGE_REVIEW, result.summary().status());
verify(pallets, org.mockito.Mockito.times(3)).insert(any(UsTruckOrderPallet.class));
}
private UsTruckOrderRequests.Create create(String key) {
return new UsTruckOrderRequests.Create("PORT_PICKUP", "Los Angeles Port", "Pier 1", "Ontario, CA",
"Warehouse A", "40HQ", 100, new BigDecimal("12600"), new BigDecimal("55.2"),
new BigDecimal("3800"), null, "Alice", "+1-555-0100", LocalDate.now().plusDays(2), null, key);
}
private UsTruckOrder order(String status, long version) {
UsTruckOrder order = new UsTruckOrder(); order.setId(1L); order.setCustomerId("100"); order.setOrderNo("TK1");
order.setTransportType("PORT_PICKUP"); order.setOrigin("Los Angeles"); order.setDestination("Ontario");
order.setContainerType("40HQ"); order.setPalletCount(1); order.setPackageCount(0);
order.setReceivedPalletCount(0); order.setAllocatedPalletCount(0); order.setShippedPalletCount(0);
order.setStatus(status); order.setVersion(version);
order.setCreatedAt(LocalDateTime.now()); order.setUpdatedAt(order.getCreatedAt()); return order;
}
}