import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { CONTAINER_CONVERT_ENDPOINT, CONTAINER_CONVERT_EXPECTED_ORDERS, CONTAINER_SUMMARY_ENDPOINT, groupOrdersByCustomer, stableReplay, validContainerBatchResult, validContainerSummaryPage, zeroDrift, } from './container-inbound-plan-batch-contract.mjs' const read = (relativeUrl) => readFileSync(new URL(relativeUrl, import.meta.url), 'utf8') 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 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') function orders() { return Array.from({ length: 15 }, (_, index) => ({ orderId: String(9_000 + index), customerId: index < 10 ? '101' : '202', packageCount: 2, volumeCbm: '1.2500', productQuantity: 20, palletCount: 1, volumeWeightKg: '18.750', estimatedArrivalAt: '2026-09-01T08:30:00', actualArrivalAt: null, estimatedWarehouseAt: null, })) } test('semantic contract creates one independent plan per order even for the same customer', () => { const source = orders() assert.equal(CONTAINER_CONVERT_EXPECTED_ORDERS, 15) assert.equal(groupOrdersByCustomer(source).size, 2) 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('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', totalOrderCount: '15', inboundCodeCount: '15', missingInboundCodeCount: 0, planCount: 0, plannedInboundCodeCount: 0, pendingPlanCount: 15, allPlansReady: false }], page: '1', size: '10', total: '1', pages: '1', }, { sourceContainerBookingId: 'CB-1', containerNo: 'C1', totalOrderCount: 15, inboundCodeCount: 15, missingInboundCodeCount: 0, planCount: 0, plannedInboundCodeCount: 0, pendingPlanCount: 15, allPlansReady: false }), true) }) 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) }) 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-container') for (const marker of ['P1整柜一次转换并逐订单生成独立计划', 'P1任一非法票整柜转换拒绝', 'P1同键同请求重放', 'P1并发双击A', 'P1部分已转换整柜冲突', 'P1服务端柜级摘要跨订单分页仍返回15票']) { assert.match(scenario, new RegExp(marker)) } assert.match(scenario, /Promise\.all/) assert.match(scenario, /Idempotency-Key/) assert.match(scenario, /requestId/) assert.match(scenario, /traceId/) }) 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: \[sourceContainerBookingId, snapshotVersion, snapshotHash\]/) assert.match(endpoint, /sourceContainerBookingId:\s*\{ type: string/) assert.match(controller, /convertContainer/) assert.match(adminApi, /sourceContainerBookingId: string/) assert.match(adminView, /const bookingId = preview\.sourceContainerBookingId/) assert.match(adminView, /const commandSnapshot = preview\.resourceRetry \?\? preview/) assert.match(adminView, /containerConversionIdempotencyKey = preview\.resourceRetry\.idempotencyKey/) assert.match(adminView, /convertLastMileContainerToInboundPlans\(\{\s*sourceContainerBookingId: bookingId,\s*snapshotVersion: commandSnapshot\.snapshotVersion,\s*snapshotHash: commandSnapshot\.snapshotHash,/) }) test('backend batch conversion is one transaction and never merges same-customer orders', () => { assert.match(controller, /ApiResponse<[^>]*(?:Batch|List|Container)[^>]*>/) assert.match(service, /@Transactional[\s\S]{0,120}(?:convertContainer|convertBatch|convert)/) assert.match(service, /(?:List|Container.*Response|Batch.*Response)/) assert.match(service, /(?:for \([^)]*(?:order|source)|\.map\([^)]*(?:order|source))/) assert.doesNotMatch(service, /groupingBy\([^)]*(?:customerId|getCustomerId)/) assert.match(service, /来源柜存在不能转换的尾程订单/) assert.match(service, /HttpStatus\.CONFLICT/) }) test('Admin consumes a server-paged container summary and never derives 15 from the current order page', () => { assert.match(adminApi, /listContainerPlanProgress/) assert.match(adminApi, /container-summaries/) assert.match(adminView, /listContainerPlanProgress\(/) assert.match(adminView, /containerRows\.value\s*=\s*result\.records/) assert.match(adminView, /containerTotal\.value\s*=\s*result\.total/) assert.match(adminView, /containerOptions\.value\s*=\s*result\.records/) assert.match(adminView, /if \(request !== containerOptionRequest\) return/) assert.doesNotMatch(adminView, /(?:rows|result\.records)\.value?\.(?:reduce|filter|forEach)[\s\S]{0,300}containerNo/) })