package com.baiyue.common.event; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import java.util.List; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; class FirstLegLoadingPlanCompletedV2Test { @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( "4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", "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"); assertThat(json.at("/sourceContainerBookingId").asText()).isEqualTo("5"); assertThat(json.has("containerBookingId")).isFalse(); 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"); } @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"); } @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", null, null, null, 1, 2, null, null, null, null, List.of(item), "11", "客户甲", "FIRST_LEG", List.of()); var payload = new FirstLegLoadingPlanCompletedV2("4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order)); 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("仓库备注"); } @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, 2, 20, 1, null, null, null, List.of(), "11", "客户甲", "ONE_CLICK_DROPSHIP", List.of(pallet)); var payload = new FirstLegLoadingPlanCompletedV2( "4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order)); var json = new ObjectMapper().valueToTree(payload); assertThat(json.at("/orders/0/customerId").asText()).isEqualTo("11"); assertThat(json.at("/orders/0/customerName").asText()).isEqualTo("客户甲"); assertThat(json.at("/orders/0/orderType").asText()).isEqualTo("ONE_CLICK_DROPSHIP"); assertThat(json.at("/orders/0/pallets/0/skuPackages/0/cartonMarks")).hasSize(2); } @Test void carriesFrozenDestinationType() { var order = new FirstLegLoadingPlanCompletedV2.Order( "2", "2", "3", "ORD", "3", "RK", null, "ONT8", "FBA ONT8", 2, 20, 0, null, null, null, List.of(), "11", "客户甲", "FIRST_LEG", List.of(), "FBA"); var json = new ObjectMapper().valueToTree(new FirstLegLoadingPlanCompletedV2( "4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order))); assertThat(json.at("/orders/0/destinationType").asText()).isEqualTo("FBA"); } @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", null, null, null, 2, 20, null, null, null, null, List.of(item), "11", "客户甲", "FIRST_LEG", List.of()); var json = new ObjectMapper().valueToTree(new FirstLegLoadingPlanCompletedV2( "4", "P", "5", "ZCSU7881970", "CAB", "6", "WH", List.of(order))); assertThat(json.at("/orders/0/items/0/skuCode").asText()).isEqualTo("SKU-PSC2"); assertThat(json.at("/orders/0/items/0/cartonMarks")).hasSize(2); } @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"); } @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"); } @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") .contains("consumers must not query domestic IAM") .contains("estimatedPortArrivalAt", "actualPortArrivalAt", "estimatedWarehouseArrivalAt") .contains("palletCount", "volumeWeightKg", "never a planned zero placeholder"); assertThat(yaml).contains("sourceContainerBookingId", "Uppercase normalized physical container number"); } }