This commit is contained in:
2026-08-27 17:16:33 +08:00
parent 61a97cd057
commit 76c24867a4
19 changed files with 474 additions and 107 deletions
@@ -5,12 +5,11 @@ import test from 'node:test'
import {
CONTAINER_CONVERT_ENDPOINT,
CONTAINER_CONVERT_EXPECTED_ORDERS,
distinctPhysicalPalletCount,
CONTAINER_SUMMARY_ENDPOINT,
groupOrdersByCustomer,
stableReplay,
validContainerBatchResult,
validOrderFact,
validPdaCandidate,
validContainerSummaryPage,
zeroDrift,
} from './container-inbound-plan-batch-contract.mjs'
@@ -19,7 +18,6 @@ const scenario = read('phase4-container-inbound-plan-batch.mjs')
const controller = read('../../lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/controller/InboundPlanController.java')
const service = read('../../lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/service/InboundPlanService.java')
const openapi = read('../../lastleg-service/src/main/resources/openapi/overseas-business-v1.yaml')
const pda = read('../../lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java')
const adminApi = read('../../../apps/admin-web/src/modules/overseas-warehouse/api/lastMileOrders.ts')
const adminView = read('../../../apps/admin-web/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue')
@@ -36,35 +34,32 @@ test('semantic contract creates one independent plan per order even for the same
const source = orders()
assert.equal(CONTAINER_CONVERT_EXPECTED_ORDERS, 15)
assert.equal(groupOrdersByCustomer(source).size, 2)
const result = { plans: source.map((order, index) => ({
const result = source.map((order, index) => ({
customerId: order.customerId, planNo: `LIP-${index + 1}`, status: 'PENDING_ASSIGNMENT',
lines: [{ orderId: order.orderId }], pallets: [{}], allocations: [{}],
})) }
}))
assert.equal(validContainerBatchResult(result, source), true)
})
test('facts preserve source values and physical pallets are not counted once per SKU', () => {
const expected = orders()[0]
assert.equal(validOrderFact({ ...expected }, expected), true)
assert.equal(validOrderFact({ ...expected, actualArrivalAt: '2026-09-02T08:00:00' }, expected), false)
assert.equal(distinctPhysicalPalletCount([
{ physicalPalletId: 'P1', sku: 'A' }, { physicalPalletId: 'P1', sku: 'B' }, { physicalPalletId: 'P2', sku: 'A' },
]), 2)
test('server container summary remains complete when the order page would contain only ten rows', () => {
assert.equal(CONTAINER_SUMMARY_ENDPOINT, '/api/platform/v1/last-leg/orders/container-summaries')
assert.equal(validContainerSummaryPage({
records: [{ sourceContainerBookingId: 'CB-1', containerNo: 'C1', orderCount: '15', inboundCodeCount: '15' }],
page: '1', size: '10', total: '1', pages: '1',
}, { sourceContainerBookingId: 'CB-1', containerNo: 'C1', orderCount: 15, inboundCodeCount: 15 }), true)
})
test('atomic failure, replay and PDA count helpers reject drift', () => {
test('atomic failure and replay helpers reject drift', () => {
const snapshot = { orders: [{ id: '1', status: 'PENDING_ASSIGNMENT' }], plans: [] }
assert.equal(zeroDrift(snapshot, structuredClone(snapshot)), true)
assert.equal(stableReplay({ plans: [{ planNo: 'LIP-1' }] }, { plans: [{ planNo: 'LIP-1' }] }), true)
assert.equal(validPdaCandidate({ containerNo: 'C1', planCount: '2', inboundCodeCount: '15' },
{ containerNo: 'C1', planCount: 2, inboundCodeCount: 15 }), true)
})
test('HTTP scenario freezes normal, invalid, replay, concurrency, partial and PDA evidence', () => {
assert.equal(CONTAINER_CONVERT_ENDPOINT, '/api/platform/v1/last-leg/inbound-plans/convert')
for (const marker of ['P0整柜一次转换并逐订单生成独立计划', 'P1任一非法票整柜转换拒绝',
assert.equal(CONTAINER_CONVERT_ENDPOINT, '/api/platform/v1/last-leg/inbound-plans/convert-container')
for (const marker of ['P1整柜一次转换并逐订单生成独立计划', 'P1任一非法票整柜转换拒绝',
'P1同键同请求重放', 'P1并发双击A', 'P1部分已转换整柜冲突',
'P0同柜PDA候选计划数与入仓码数一致', 'P3箱码服务端命中当前柜唯一入仓码']) {
'P1服务端柜级摘要跨订单分页仍返回15票']) {
assert.match(scenario, new RegExp(marker))
}
assert.match(scenario, /Promise\.all/)
@@ -73,17 +68,14 @@ test('HTTP scenario freezes normal, invalid, replay, concurrency, partial and PD
assert.match(scenario, /traceId/)
})
test('platform API keeps two long string identifiers and Admin uses the selected row customer', () => {
test('container API uses one stable long string identity', () => {
const endpoint = openapi.slice(openapi.indexOf(` ${CONTAINER_CONVERT_ENDPOINT}:`),
openapi.indexOf(' /api/platform/v1/last-leg/dispatches:', openapi.indexOf(` ${CONTAINER_CONVERT_ENDPOINT}:`)))
assert.match(endpoint, /required: \[customerId, orderId\]/)
assert.match(endpoint, /customerId:\s*\{ type: string/)
assert.match(endpoint, /orderId:\s*\{ type: string/)
assert.match(controller, /convertScope\(user, request\.customerId\(\)\)/)
assert.match(adminApi, /orderId: string[\s\S]{0,80}customerId: string/)
assert.match(adminView, /selectedRows\.value\.length !== 1/)
assert.match(adminView, /customerId = typeof selected\.customerId === 'string' \? selected\.customerId\.trim\(\)/)
assert.match(adminView, /orderId: String\(selected\.id\), customerId/)
assert.match(endpoint, /required: \[sourceContainerBookingId\]/)
assert.match(endpoint, /sourceContainerBookingId:\s*\{ type: string/)
assert.match(controller, /convertContainer/)
assert.match(adminApi, /sourceContainerBookingId: string/)
assert.match(adminView, /sourceContainerBookingId: row\.sourceContainerBookingId/)
})
test('backend batch conversion is one transaction and never merges same-customer orders', () => {
@@ -92,13 +84,14 @@ test('backend batch conversion is one transaction and never merges same-customer
assert.match(service, /(?:List<InboundPlanResponse>|Container.*Response|Batch.*Response)/)
assert.match(service, /(?:for \([^)]*(?:order|source)|\.map\([^)]*(?:order|source))/)
assert.doesNotMatch(service, /groupingBy\([^)]*(?:customerId|getCustomerId)/)
assert.match(service, /(?:partial|部分已转换|already belongs)[\s\S]{0,180}(?:CONFLICT|HttpStatus\.CONFLICT)/i)
assert.match(service, /来源柜存在不能转换的尾程订单/)
assert.match(service, /HttpStatus\.CONFLICT/)
})
test('PDA candidate and resolve contracts remain server-owned', () => {
assert.match(pda, /grouped\.computeIfAbsent\(plan\.getContainerNo\(\)/)
assert.match(pda, /entry\.getValue\(\)\.size\(\)/)
assert.match(pda, /totals\.inboundCount\(\)/)
assert.match(pda, /requireReceivingAvailable\(customerId, containerNo\)/)
assert.match(pda, /requireCartonRef|requireUnmatchedFbaCartonPrefix/)
test('Admin consumes a server-paged container summary and never derives 15 from the current order page', () => {
assert.match(adminApi, /(?:listLastMileContainerSummaries|listContainerSummaries)/)
assert.match(adminApi, /container-summaries/)
assert.match(adminView, /(?:listLastMileContainerSummaries|listContainerSummaries)\(/)
assert.match(adminView, /containerRows\.value\s*=\s*result\.records/)
assert.doesNotMatch(adminView, /(?:rows|result\.records)\.value?\.(?:reduce|filter|forEach)[\s\S]{0,300}containerNo/)
})