2026-08-18 16:09:52 +08:00
|
|
|
package com.baiyue.common.event;
|
|
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
|
|
|
|
|
import java.util.List;
|
2026-08-27 16:42:27 +08:00
|
|
|
import java.math.BigDecimal;
|
2026-08-27 15:56:14 +08:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2026-08-18 16:09:52 +08:00
|
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
2026-08-27 15:56:14 +08:00
|
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
2026-08-18 16:09:52 +08:00
|
|
|
|
|
|
|
|
class FirstLegLoadingPlanCompletedV2Test {
|
2026-08-27 16:42:27 +08:00
|
|
|
@Test
|
|
|
|
|
void carriesOnlyFrozenContainerTimesAndActualOrderAggregates() {
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, "ONT8", "ONT8",
|
|
|
|
|
12, 120, 3, null, null, new BigDecimal("2.500"), new BigDecimal("18.750"),
|
|
|
|
|
List.of(), "11", "客户甲", "FIRST_LEG", List.of(), "OVERSEAS");
|
|
|
|
|
var payload = new FirstLegLoadingPlanCompletedV2(
|
2026-08-31 12:13:20 +08:00
|
|
|
"4", "P", "5", "ZCSU7881970", "CAB", "6", "WH",
|
2026-08-27 16:42:27 +08:00
|
|
|
"2026-09-30T12:00", null, "2026-10-03T09:00", List.of(order));
|
|
|
|
|
|
|
|
|
|
var json = new ObjectMapper().valueToTree(payload);
|
|
|
|
|
|
|
|
|
|
assertThat(json.at("/estimatedPortArrivalAt").asText()).isEqualTo("2026-09-30T12:00");
|
|
|
|
|
assertThat(json.at("/actualPortArrivalAt").isNull()).isTrue();
|
|
|
|
|
assertThat(json.at("/estimatedWarehouseArrivalAt").asText()).isEqualTo("2026-10-03T09:00");
|
2026-08-31 12:13:20 +08:00
|
|
|
assertThat(json.at("/sourceContainerBookingId").asText()).isEqualTo("5");
|
|
|
|
|
assertThat(json.has("containerBookingId")).isFalse();
|
2026-08-27 16:42:27 +08:00
|
|
|
assertThat(json.at("/orders/0/palletCount").asInt()).isEqualTo(3);
|
|
|
|
|
assertThat(json.at("/orders/0/packageCount").asInt()).isEqualTo(12);
|
|
|
|
|
assertThat(json.at("/orders/0/productQuantity").asInt()).isEqualTo(120);
|
|
|
|
|
assertThat(json.at("/orders/0/volumeCbm").decimalValue()).isEqualByComparingTo("2.500");
|
|
|
|
|
assertThat(json.at("/orders/0/volumeWeightKg").decimalValue()).isEqualByComparingTo("18.750");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 12:13:20 +08:00
|
|
|
@Test
|
|
|
|
|
void rejectsNonCanonicalOrMissingPhysicalContainerIdentity() {
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, "ONT8", "ONT8",
|
|
|
|
|
1, 1, null, null, null, null, List.of(), "11", "客户甲", "FIRST_LEG", List.of());
|
|
|
|
|
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2(
|
|
|
|
|
"4", "P", "5", " zcsu7881970 ", "CAB", "6", "WH", List.of(order)))
|
|
|
|
|
.hasMessageContaining("normalized");
|
|
|
|
|
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2(
|
|
|
|
|
"4", "P", "5", " ", "CAB", "6", "WH", List.of(order)))
|
|
|
|
|
.hasMessageContaining("containerNo");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-18 16:09:52 +08:00
|
|
|
@Test
|
|
|
|
|
void carriesTheThreeItemRemarks() throws Exception {
|
|
|
|
|
var item = new FirstLegLoadingPlanCompletedV2.Item("1", "SKU", "中文", "EN", 1,
|
|
|
|
|
2, null, null, null, null, null, null, null, null, null, null, null,
|
|
|
|
|
"客户备注", "内部备注", "仓库备注");
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order("2", "2", "3", "ORD", "3", "RK",
|
2026-08-27 15:56:14 +08:00
|
|
|
null, null, null, 1, 2, null, null, null, null, List.of(item),
|
|
|
|
|
"11", "客户甲", "FIRST_LEG", List.of());
|
2026-08-31 12:13:20 +08:00
|
|
|
var payload = new FirstLegLoadingPlanCompletedV2("4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order));
|
2026-08-18 16:09:52 +08:00
|
|
|
String json = new ObjectMapper().writeValueAsString(payload);
|
|
|
|
|
assertThat(json).contains("customerRemark", "internalRemark", "warehouseRemark");
|
|
|
|
|
assertThat(new ObjectMapper().readTree(json).at("/orders/0/items/0/warehouseRemark").asText())
|
|
|
|
|
.isEqualTo("仓库备注");
|
|
|
|
|
}
|
2026-08-19 14:02:11 +08:00
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void carriesDropshipPalletAndCartonSnapshots() throws Exception {
|
|
|
|
|
var sku = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(
|
|
|
|
|
"SKU", "产品", 2, 20, List.of("BOX-001", "BOX-002"));
|
|
|
|
|
var pallet = new FirstLegLoadingPlanCompletedV2.PalletSnapshot("PLT-01", List.of(sku));
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, null, null,
|
2026-08-27 15:56:14 +08:00
|
|
|
2, 20, 1, null, null, null, List.of(), "11", "客户甲", "ONE_CLICK_DROPSHIP", List.of(pallet));
|
2026-08-19 14:02:11 +08:00
|
|
|
var payload = new FirstLegLoadingPlanCompletedV2(
|
2026-08-31 12:13:20 +08:00
|
|
|
"4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order));
|
2026-08-19 14:02:11 +08:00
|
|
|
|
|
|
|
|
var json = new ObjectMapper().valueToTree(payload);
|
|
|
|
|
|
|
|
|
|
assertThat(json.at("/orders/0/customerId").asText()).isEqualTo("11");
|
2026-08-27 15:56:14 +08:00
|
|
|
assertThat(json.at("/orders/0/customerName").asText()).isEqualTo("客户甲");
|
2026-08-19 14:02:11 +08:00
|
|
|
assertThat(json.at("/orders/0/orderType").asText()).isEqualTo("ONE_CLICK_DROPSHIP");
|
|
|
|
|
assertThat(json.at("/orders/0/pallets/0/skuPackages/0/cartonMarks")).hasSize(2);
|
|
|
|
|
}
|
2026-08-20 16:45:47 +08:00
|
|
|
|
2026-08-24 17:15:48 +08:00
|
|
|
@Test
|
|
|
|
|
void carriesFrozenDestinationType() {
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, "ONT8", "FBA ONT8",
|
2026-08-27 15:56:14 +08:00
|
|
|
2, 20, 0, null, null, null, List.of(), "11", "客户甲", "FIRST_LEG", List.of(), "FBA");
|
2026-08-24 17:15:48 +08:00
|
|
|
|
|
|
|
|
var json = new ObjectMapper().valueToTree(new FirstLegLoadingPlanCompletedV2(
|
2026-08-31 12:13:20 +08:00
|
|
|
"4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order)));
|
2026-08-24 17:15:48 +08:00
|
|
|
|
|
|
|
|
assertThat(json.at("/orders/0/destinationType").asText()).isEqualTo("FBA");
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-20 16:45:47 +08:00
|
|
|
@Test
|
|
|
|
|
void mapsEachOrdinaryCartonNumberToItsSku() {
|
|
|
|
|
var item = new FirstLegLoadingPlanCompletedV2.Item("1", "SKU-PSC2", "产品", "Product", 2,
|
|
|
|
|
20, null, null, null, null, null, null, null, null, null, null, null,
|
|
|
|
|
null, null, null, null, List.of("HDHK260711126-01", "HDHK260711126-02"));
|
|
|
|
|
var order = new FirstLegLoadingPlanCompletedV2.Order("2", "2", "3", "ORD", "3", "RK",
|
2026-08-27 15:56:14 +08:00
|
|
|
null, null, null, 2, 20, null, null, null, null, List.of(item),
|
|
|
|
|
"11", "客户甲", "FIRST_LEG", List.of());
|
2026-08-20 16:45:47 +08:00
|
|
|
|
|
|
|
|
var json = new ObjectMapper().valueToTree(new FirstLegLoadingPlanCompletedV2(
|
2026-08-31 12:13:20 +08:00
|
|
|
"4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order)));
|
2026-08-20 16:45:47 +08:00
|
|
|
|
|
|
|
|
assertThat(json.at("/orders/0/items/0/skuCode").asText()).isEqualTo("SKU-PSC2");
|
|
|
|
|
assertThat(json.at("/orders/0/items/0/cartonMarks")).hasSize(2);
|
|
|
|
|
}
|
2026-08-27 15:56:14 +08:00
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void rejectsMissingImmutableCustomerName() {
|
|
|
|
|
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, null, null,
|
|
|
|
|
1, 1, 0, null, null, null, List.of(), "11", " ", "FIRST_LEG", List.of()))
|
|
|
|
|
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("customerName");
|
2026-09-01 11:06:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void freezesTrimmedOwnerSnapshotAndRejectsMixedOwners() {
|
|
|
|
|
var first = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"2", "2", "3", "ORD", "3", "RK", null, null, null,
|
|
|
|
|
1, 1, 0, null, null, null, List.of(), " 11 ", " 客户甲 ", "FIRST_LEG", List.of());
|
|
|
|
|
assertThat(first.customerId()).isEqualTo("11");
|
|
|
|
|
assertThat(first.customerName()).isEqualTo("客户甲");
|
|
|
|
|
|
|
|
|
|
var anotherOwner = new FirstLegLoadingPlanCompletedV2.Order(
|
|
|
|
|
"3", "3", "4", "ORD-2", "4", "RK-2", null, null, null,
|
|
|
|
|
1, 1, 0, null, null, null, List.of(), "12", "客户乙", "FIRST_LEG", List.of());
|
|
|
|
|
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2(
|
|
|
|
|
"4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(first, anotherOwner)))
|
|
|
|
|
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("immutable customerId/customerName");
|
2026-08-27 15:56:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
void openApiRequiresImmutableCustomerNameNextToCustomerId() throws Exception {
|
|
|
|
|
String yaml;
|
|
|
|
|
try (var input = FirstLegLoadingPlanCompletedV2Test.class.getResourceAsStream(
|
|
|
|
|
"/openapi/cross-region-events-v1.yaml")) {
|
|
|
|
|
assertThat(input).isNotNull();
|
|
|
|
|
yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
|
|
|
|
|
}
|
|
|
|
|
assertThat(yaml)
|
|
|
|
|
.contains("required: [sourceInboundId, customerId, customerName")
|
|
|
|
|
.contains("customerId: {type: string, minLength: 1")
|
|
|
|
|
.contains("customerName: {type: string, minLength: 1, maxLength: 128")
|
2026-08-27 16:42:27 +08:00
|
|
|
.contains("consumers must not query domestic IAM")
|
|
|
|
|
.contains("estimatedPortArrivalAt", "actualPortArrivalAt", "estimatedWarehouseArrivalAt")
|
|
|
|
|
.contains("palletCount", "volumeWeightKg", "never a planned zero placeholder");
|
2026-08-31 12:13:20 +08:00
|
|
|
assertThat(yaml).contains("sourceContainerBookingId", "Uppercase normalized physical container number");
|
2026-08-27 15:56:14 +08:00
|
|
|
}
|
2026-08-18 16:09:52 +08:00
|
|
|
}
|