fix: complete phase5 overseas deployment contract

This commit is contained in:
2026-09-01 20:20:25 +08:00
45 changed files with 810 additions and 638 deletions
@@ -65,8 +65,9 @@ class DeliverySchemaContractTest {
String overseas = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"),
StandardCharsets.UTF_8).toLowerCase();
assertTrue(overseas.contains("空库先执行 lastleg-service/src/main/resources/db/lastleg.sql 基线"));
assertFalse(overseas.contains(createToken));
assertFalse(overseas.contains("alter table ll_delivery_order"));
assertTrue(overseas.contains(createToken));
assertTrue(overseas.indexOf(createToken) < overseas.indexOf("assert_phase5_lastleg_identifiers_migratable"));
assertTrue(overseas.contains("alter table ll_delivery_order add column overseas_warehouse_name"));
}
@Test
@@ -0,0 +1,74 @@
package com.baiyue.lastleg.dropship.controller;
import com.baiyue.lastleg.dropship.dto.DropshipRequests;
import com.baiyue.lastleg.dropship.service.DropshipOrderService;
import com.baiyue.lastleg.security.InternalServiceTokenGuard;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class DropshipOrderControllerContractTest {
@Test
void routeRequiresTheFrozenInternalTokenHeaderBeforeCallingTheService() throws Exception {
RequestMapping base = DropshipOrderController.class.getAnnotation(RequestMapping.class);
Method receive = DropshipOrderController.class.getMethod("receive", String.class,
DropshipRequests.Receive.class);
RequestHeader token = receive.getParameters()[0].getAnnotation(RequestHeader.class);
assertThat(base.value()).containsExactly("/internal/v1/lastleg/dropship");
assertThat(receive.getAnnotation(PostMapping.class).value()).containsExactly("/receive");
assertThat(token.value()).isEqualTo("X-Internal-Service-Token");
InternalServiceTokenGuard guard = mock(InternalServiceTokenGuard.class);
DropshipOrderService service = mock(DropshipOrderService.class);
DropshipOrderController controller = new DropshipOrderController();
ReflectionTestUtils.setField(controller, "tokenGuard", guard);
ReflectionTestUtils.setField(controller, "service", service);
DropshipRequests.Receive request = request();
when(service.receive(request)).thenReturn(1);
assertThat(controller.receive("internal-token", request).data()).isEqualTo(1);
InOrder order = inOrder(guard, service);
order.verify(guard).verify("internal-token");
order.verify(service).receive(request);
}
@Test
void versionedOpenApiMatchesTheControllerAndDtoContract() throws Exception {
String yaml = Files.readString(
Path.of("src/main/resources/openapi/lastleg-internal-dropship-v1.yaml"),
StandardCharsets.UTF_8);
assertThat(yaml).contains(
"version: 1.0.0",
"/internal/v1/lastleg/dropship/receive:",
"name: X-Internal-Service-Token",
"required: [customerId, orderId, warehouseId, idempotencyKey, pallets]",
"required: [palletNo, skuPackages]",
"required: [skuCode, packageCount, productQuantity, cartonMarks]",
"data: { type: integer, format: int32, minimum: 0 }");
assertThat(yaml.indexOf("name: X-Internal-Service-Token"))
.isLessThan(yaml.indexOf("requestBody:"));
}
private static DropshipRequests.Receive request() {
return new DropshipRequests.Receive("100", 10L, 20L, "event-1",
List.of(new DropshipRequests.Pallet("PALLET-1",
List.of(new DropshipRequests.SkuPackages("SKU-1", "Product", 1, 2,
List.of("CARTON-1"))))));
}
}
@@ -0,0 +1,31 @@
package com.baiyue.lastleg.schema;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class Phase5ExceptionVisitSchemaContractTest {
@Test
void baselineAndOfficialOverseasEntryDefineExceptionAndVisitPersistence() throws Exception {
String baseline = Files.readString(Path.of("src/main/resources/db/lastleg.sql"), StandardCharsets.UTF_8);
String overseas = Files.readString(Path.of("..", "deploy", "sql", "海外区域统一增量-MySQL8.sql"),
StandardCharsets.UTF_8);
for (String sql : new String[] { baseline, overseas }) {
assertThat(sql).contains(
"CREATE TABLE IF NOT EXISTS ll_exception_order",
"UNIQUE KEY uk_ll_exception_no(customer_id,exception_no)",
"UNIQUE KEY uk_ll_exception_delivery_return(customer_id,delivery_order_id,exception_type)",
"KEY idx_ll_exception_inbound(customer_id,inbound_code)",
"CREATE TABLE IF NOT EXISTS ll_customer_visit",
"UNIQUE KEY uk_ll_customer_visit_delivery(customer_id,delivery_order_id)",
"KEY idx_ll_customer_visit_list(customer_id,status,delivery_completed_at)",
"COMMENT='海外仓异常订单'",
"COMMENT='派送单维度客户回访记录'");
}
}
}
@@ -0,0 +1,108 @@
package com.baiyue.lastleg.schema;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import static org.assertj.core.api.Assertions.assertThat;
class Phase5OverseasUpgradeSqlContractTest {
private static final List<String> PHASE5_TABLES = List.of(
"ll_dispatch_pda_task", "ll_dispatch_pda_pick", "ll_dispatch_pda_operation",
"ll_dispatch_pda_pick_order", "ll_outbound_order", "ll_outbound_pallet_item",
"ll_outbound_pda_task", "ll_outbound_pda_pick", "ll_delivery_order", "ll_delivery_fee",
"ll_delivery_operation", "ll_dropship_order_profile", "ll_dropship_carton",
"ll_dropship_sku_inventory", "ll_dropship_carton_ledger", "ll_exception_order",
"ll_customer_visit");
@Test
void overseasFixedEntryCreatesEveryPhase5TableBeforeAnyPhase5AlterOrAssertion() throws Exception {
String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql");
int phase5Gate = overseas.indexOf("-- 五期排车、出库与海外PDA正式业务号/幂等约束");
assertThat(phase5Gate).isPositive();
for (String table : PHASE5_TABLES) {
String create = "CREATE TABLE IF NOT EXISTS " + table + " (";
assertThat(overseas.indexOf(create)).as(table).isBetween(0, phase5Gate - 1);
assertThat(count(overseas, create)).as(table).isEqualTo(1);
assertThat(overseas.indexOf(table)).as(table + " first reference")
.isEqualTo(overseas.indexOf(create) + "CREATE TABLE IF NOT EXISTS ".length());
}
assertThat(overseas).contains("<> 22", "五期完整业务表集合未部署");
}
@Test
void overseasFixedEntryConvergesKnownIntermediatePhase5ShapesWithoutBackfill() throws Exception {
String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql");
assertThat(overseas).contains(
"ALTER TABLE ll_outbound_order RENAME COLUMN tenant_id TO customer_id",
"ALTER TABLE ll_outbound_pallet_item RENAME COLUMN tenant_id TO customer_id",
"ALTER TABLE ll_delivery_order RENAME COLUMN tenant_id TO customer_id",
"ALTER TABLE ll_outbound_order ADD COLUMN outbound_type",
"ALTER TABLE ll_outbound_pallet_item ADD COLUMN picked_package_count",
"ALTER TABLE ll_delivery_order ADD COLUMN overseas_warehouse_name",
"ALTER TABLE ll_delivery_order ADD COLUMN waybill_no",
"ALTER TABLE ll_dropship_order_profile ADD COLUMN source_pallet_no",
"ADD UNIQUE KEY uk_ll_dropship_sku_inventory(profile_id,sku_code)",
"MODIFY COLUMN source_pallet_no VARCHAR(128) NOT NULL");
int start = overseas.indexOf("-- 五期正式结构必须由海外固定入口自行补齐");
int end = overseas.indexOf("USE baiyue_lastleg;", start);
String phase5 = overseas.substring(start, end);
assertThat(phase5).doesNotContain("\nUPDATE ", "\nDELETE ", "\nINSERT ");
}
@Test
void overseasFixedEntryUsesTheCurrentBaselineDefinitions() throws Exception {
String baseline = repositoryFile("lastleg-service/src/main/resources/db/lastleg.sql");
String overseas = repositoryFile("deploy/sql/海外区域统一增量-MySQL8.sql");
for (String table : PHASE5_TABLES) {
assertThat(normalize(createStatement(overseas, table)))
.as(table)
.isEqualTo(normalize(createStatement(baseline, table)));
}
}
@Test
void domesticFixedEntryKeepsIamPermissionsButOwnsNoLastlegBusinessSchema() throws Exception {
String domestic = repositoryFile("deploy/sql/头程数据库统一增量-MySQL8.sql");
assertThat(domestic).contains(
"platform:overseas-warehouse:dispatch:list",
"platform:overseas-warehouse:exception-order:list",
"platform:overseas-warehouse:customer-visit:list");
assertThat(domestic).doesNotContain(
"USE baiyue_lastleg",
"CREATE DATABASE IF NOT EXISTS baiyue_lastleg",
"CREATE TABLE IF NOT EXISTS ll_",
"ALTER TABLE ll_");
}
private static String createStatement(String sql, String table) {
String token = "CREATE TABLE IF NOT EXISTS " + table + " (";
int start = sql.indexOf(token);
int end = sql.indexOf(';', start);
assertThat(start).as(table).isGreaterThanOrEqualTo(0);
assertThat(end).as(table).isGreaterThan(start);
return sql.substring(start, end + 1);
}
private static String normalize(String sql) {
return sql.toLowerCase(Locale.ROOT).replaceAll("\\s+", "");
}
private static int count(String source, String token) {
return source.split(java.util.regex.Pattern.quote(token), -1).length - 1;
}
private static String repositoryFile(String relative) throws Exception {
Path path = Path.of(relative);
if (!Files.exists(path)) path = Path.of("..").resolve(relative);
return Files.readString(path, StandardCharsets.UTF_8);
}
}