feat(order): formalize domestic order export contract

This commit is contained in:
2026-09-01 20:48:44 +08:00
parent 94c67b5c51
commit 858061452c
5 changed files with 313 additions and 0 deletions
@@ -0,0 +1,41 @@
package com.baiyue.order.controller;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
class DomesticWarehouseOrderExportOpenApiContractTest {
@Test
void documentsAsyncProviderSelectionPrecedenceAndListFilters() throws Exception {
String source = new ClassPathResource("openapi/order-v1.yaml")
.getContentAsString(StandardCharsets.UTF_8);
Map<String, Object> document = map(new Yaml().load(source));
Map<String, Object> paths = map(document.get("paths"));
Map<String, Object> operation = map(map(paths.get(
"/internal/exports/v1/DOMESTIC_WAREHOUSE_ORDER_LIST/batches")).get("post"));
assertThat(operation.get("description").toString())
.contains("selectedOrderNos 非空时忽略查询筛选", "不生成文件",
"DOMESTIC_WAREHOUSE_ORDER_LIST", "platform:order:export");
Map<String, Object> schemas = map(map(document.get("components")).get("schemas"));
Map<String, Object> parameters = map(map(
schemas.get("DomesticWarehouseOrderExportParameters")).get("properties"));
assertThat(parameters).containsKeys(
"selectedOrderNos", "tenantId", "keyword", "orderNo", "mainStatus", "customerId",
"receivingWarehouseId", "destinationWarehouseId", "inboundCode", "internalOrderStatus",
"expectedArrivalDate", "feeReviewStatus", "channelName", "containerNo", "cabinetNo",
"sailingDate", "loaded", "fullContainer", "measurementReviewStatus", "fbaCode",
"createdFrom", "createdTo");
assertThat(parameters).doesNotContainKeys("page", "size", "orderId", "customerInternalId");
}
@SuppressWarnings("unchecked")
private Map<String, Object> map(Object value) {
return (Map<String, Object>) value;
}
}
@@ -0,0 +1,43 @@
package com.baiyue.order.controller;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.export.ExportContracts.ExportBatchRequest;
import com.baiyue.common.core.export.ExportContracts.ExportBatchResponse;
import com.baiyue.order.service.DomesticWarehouseOrderExportProviderService;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(DomesticWarehouseOrderExportProviderController.class)
@TestPropertySource(properties = "baiyue.security.internal-service-token=0123456789abcdef0123456789abcdef")
class DomesticWarehouseOrderExportProviderControllerTest {
private static final String TOKEN = "0123456789abcdef0123456789abcdef";
@MockBean private DomesticWarehouseOrderExportProviderService service;
@Resource private DomesticWarehouseOrderExportProviderController controller;
@Test
void acceptsOnlyConfiguredInternalServiceToken() {
ExportBatchRequest request = new ExportBatchRequest(
"1001", "0", "PLATFORM", "7001", 1, Map.of(), null, null, 100);
ExportBatchResponse response = new ExportBatchResponse(
1, "hash", "snapshot", List.of(), List.of(), null, false, 0L);
when(service.exportBatch(request)).thenReturn(response);
assertThat(controller.batch(TOKEN, request).data()).isSameAs(response);
verify(service).exportBatch(request);
assertThatThrownBy(() -> controller.batch("wrong-token", request))
.isInstanceOf(BizException.class)
.hasMessageContaining("Invalid internal service token");
}
}
@@ -0,0 +1,145 @@
package com.baiyue.order.service;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.common.core.export.ExportContracts.ExportBatchRequest;
import com.baiyue.common.core.export.ExportContracts.ExportBatchResponse;
import com.baiyue.order.dto.UnifiedOrderQuery;
import com.baiyue.order.dto.UnifiedOrderResponse;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SpringJUnitConfig(DomesticWarehouseOrderExportProviderService.class)
@TestPropertySource(properties = "baiyue.security.internal-service-token=0123456789abcdef0123456789abcdef")
class DomesticWarehouseOrderExportProviderServiceTest {
@MockBean private UnifiedOrderQueryService orders;
@Resource private DomesticWarehouseOrderExportProviderService service;
@Test
void nonEmptySelectionExportsOnlySelectedBusinessOrdersAndIgnoresFilters() {
UnifiedOrderResponse.Row first = row("ORD-2", "RK-2", "客户乙");
UnifiedOrderResponse.Row second = row("ORD-1", "RK-1", "客户甲");
when(orders.platformRowsByOrderNos(List.of("ORD-2", "ORD-1")))
.thenReturn(List.of(first, second));
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("selectedOrderNos", List.of("ORD-2", "ORD-1"));
parameters.put("mainStatus", "PENDING_REVIEW");
ExportBatchResponse response = service.exportBatch(request("1001", parameters, null, null, 500));
assertThat(response.totalRows()).isEqualTo(2);
assertThat(response.rows()).extracting(row -> row.get("orderNo"))
.containsExactly("ORD-2", "ORD-1");
assertThat(response.rows().get(0)).containsEntry("inboundCode", "RK-2")
.containsEntry("customerName", "客户乙")
.doesNotContainKeys("orderId", "tenantId", "customerId", "inboundId",
"receivingWarehouseId", "destinationWarehouseId");
assertThat(response.columns()).extracting(column -> column.key())
.doesNotContain("orderId", "tenantId", "customerId", "inboundId");
verify(orders, never()).platformPage(any(), any());
}
@Test
void emptySelectionExportsAllMatchingFiltersWithProviderPaging() {
UnifiedOrderResponse.Row row = row("ORD-3", "RK-3", "客户丙");
when(orders.platformPage(eq(9001L), any()))
.thenReturn(new PageResult<>(List.of(row), 1, 500, 501, 2));
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("selectedOrderNos", List.of());
parameters.put("tenantId", "9001");
parameters.put("keyword", "RK-3");
parameters.put("loaded", true);
parameters.put("createdFrom", "2026-08-01T00:00:00");
ExportBatchResponse response = service.exportBatch(request("1002", parameters, null, null, 500));
ArgumentCaptor<UnifiedOrderQuery> query = ArgumentCaptor.forClass(UnifiedOrderQuery.class);
verify(orders).platformPage(eq(9001L), query.capture());
assertThat(query.getValue().keyword()).isEqualTo("RK-3");
assertThat(query.getValue().loaded()).isTrue();
assertThat(query.getValue().page()).isEqualTo(1);
assertThat(query.getValue().size()).isEqualTo(500);
assertThat(response.totalRows()).isEqualTo(501);
assertThat(response.hasMore()).isTrue();
assertThat(response.nextCursor()).isEqualTo("2");
}
@Test
void selectionMustBeUniqueAndEntirelyVisible() {
assertThatThrownBy(() -> service.exportBatch(request("1003",
Map.of("selectedOrderNos", List.of("ORD-1", "ORD-1")), null, null, 100)))
.isInstanceOf(BizException.class)
.hasMessageContaining("不能重复");
UnifiedOrderResponse.Row visible = row("ORD-1", "RK-1", "客户甲");
when(orders.platformRowsByOrderNos(List.of("ORD-1", "ORD-MISSING")))
.thenReturn(List.of(visible));
assertThatThrownBy(() -> service.exportBatch(request("1004",
Map.of("selectedOrderNos", List.of("ORD-1", "ORD-MISSING")), null, null, 100)))
.isInstanceOf(BizException.class)
.hasMessageContaining("不存在或不可见");
}
@Test
void filteredExportRejectsEmptyAndOverLimitResults() {
when(orders.platformPage(any(), any()))
.thenReturn(new PageResult<>(List.of(), 1, 100, 0, 0));
assertThatThrownBy(() -> service.exportBatch(request("1005", Map.of(), null, null, 100)))
.isInstanceOf(BizException.class)
.hasMessageContaining("没有符合");
when(orders.platformPage(any(), any()))
.thenReturn(new PageResult<>(List.of(), 1, 100, 200_001, 2001));
assertThatThrownBy(() -> service.exportBatch(request("1006", Map.of(), null, null, 100)))
.isInstanceOf(BizException.class)
.hasMessageContaining("超过最大限制");
}
@Test
void continuationSnapshotCannotBeReusedForDifferentTaskOrParameters() {
UnifiedOrderResponse.Row row = row("ORD-1", "RK-1", "客户甲");
when(orders.platformPage(any(), any()))
.thenReturn(new PageResult<>(List.of(row), 1, 1, 2, 2));
ExportBatchResponse first = service.exportBatch(request("1007", Map.of("keyword", "A"), null, null, 1));
assertThatThrownBy(() -> service.exportBatch(
request("1008", Map.of("keyword", "A"), "2", first.snapshotToken(), 1)))
.isInstanceOf(BizException.class)
.hasMessageContaining("does not match");
assertThatThrownBy(() -> service.exportBatch(
request("1007", Map.of("keyword", "B"), "2", first.snapshotToken(), 1)))
.isInstanceOf(BizException.class)
.hasMessageContaining("does not match");
}
private ExportBatchRequest request(String taskId, Map<String, Object> parameters,
String cursor, String snapshot, int limit) {
return new ExportBatchRequest(taskId, "0", "PLATFORM", "7001", 1,
parameters, cursor, snapshot, limit);
}
private UnifiedOrderResponse.Row row(String orderNo, String inboundCode, String customerName) {
UnifiedOrderResponse.Row row = mock(UnifiedOrderResponse.Row.class);
when(row.orderNo()).thenReturn(orderNo);
when(row.inboundCode()).thenReturn(inboundCode);
when(row.customerName()).thenReturn(customerName);
return row;
}
}