Files
baiyue-firstleg/scripts/admin-test-env-modules/phase3-order-splitting-contract.test.mjs
T

104 lines
6.8 KiB
JavaScript
Raw Normal View History

2026-08-14 10:09:46 +08:00
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
const source = readFileSync(new URL('./phase3-order-splitting.mjs', import.meta.url), 'utf8')
2026-08-14 23:21:47 +08:00
test('each remainder uses a simulation linked to a confirmed then released booking', () => {
assert.match(source, /POST', CONTAINERS/)
2026-08-14 10:09:46 +08:00
assert.match(source, /POST', '\/api\/platform\/v1\/ocean-bookings'/)
assert.match(source, /一次登记直接返回 CONFIRMED/)
assert.doesNotMatch(source, /ocean-bookings\/\$\{oceanBookingId\}\/(?:submit|confirm)/)
const createPayload = source.slice(source.indexOf('function bookingPayload'), source.indexOf('async function loadOneFragment'))
2026-08-15 11:51:15 +08:00
assert.match(createPayload, /oceanBookingDraft\(\{ marker \}/)
assert.match(createPayload, /customerId: source\.tenantId/)
assert.match(createPayload, /channelCode: source\.channelCode/)
assert.match(source, /ocean-booking-link/)
2026-08-14 23:21:47 +08:00
assert.match(source, /bookingDetail\.oceanBooking\.status === 'CONFIRMED'/)
assert.match(source, /`\$\{bookingPath\}\/release`/)
const releaseStart = source.indexOf('const releaseResponse')
const releaseBlock = source.slice(releaseStart, source.indexOf('bookingDetailResponse = await', releaseStart))
for (const field of ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt',
2026-08-15 12:12:34 +08:00
'estimatedArrivalAt']) assert.match(releaseBlock, new RegExp(`${field}: persistedTransport\\.${field}`))
assert.match(releaseBlock, /expectedVersion: Number\(bookingDetail\.oceanBooking\.version\)/)
assert.doesNotMatch(releaseBlock, /BL-\$\{|SPLIT-VESSEL|SV-\$\{|2026-/)
2026-08-14 23:21:47 +08:00
assert.match(releaseBlock, /'Idempotency-Key': `\$\{operationBase\}-booking-release`\.slice\(0, 128\)/)
assert.match(source, /oceanBooking\?\.status === 'RELEASED' && releasedAllocationMatches/)
assert.match(source, /id\(item\?\.containerBookingId\) === containerId/)
2026-08-14 10:09:46 +08:00
assert.match(source, /candidates\?bookingId=\$\{encodeURIComponent\(containerId\)\}&warehouseId=\$\{encodeURIComponent\(warehouseId\)\}/)
assert.match(source, /`\$\{SMART\}\/plans`, \{ bookingId: containerId/)
assert.doesNotMatch(source, /candidates\?warehouseId=/)
})
2026-08-15 12:12:34 +08:00
test('release stops unless creation response and detail expose the same complete transport snapshot', () => {
assert.match(source, /const BOOKING_TRANSPORT_FIELDS = \[/)
assert.match(source, /const createdTransport = bookingTransportSnapshot\(bookingCreated\?\.oceanBooking\)/)
assert.match(source, /const persistedTransport = bookingTransportSnapshot\(bookingDetail\?\.oceanBooking\)/)
assert.match(source, /completeBookingTransport\(createdTransport\)/)
assert.match(source, /sameBookingTransport\(createdTransport, persistedTransport\)/)
assert.match(source, /if \(!transportFrozen\) return null/)
})
2026-08-14 10:09:46 +08:00
test('PDA opens only after a real container number and uses stable plan identity', () => {
const pushIndex = source.indexOf("`${SMART}/plans/${planId}/push-preload`")
const transportIndex = source.indexOf("`${CONTAINERS}/${encodeURIComponent(containerId)}/transport`")
2026-08-14 23:21:47 +08:00
const releaseIndex = source.indexOf("`${bookingPath}/release`")
2026-08-15 17:40:01 +08:00
const confirmIndex = source.indexOf("`/api/platform/v1/first-leg/loading-plans/${encodeURIComponent(planId)}/confirm`")
2026-08-14 10:09:46 +08:00
const taskIndex = source.indexOf("`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`")
2026-08-15 17:40:01 +08:00
const canonicalTaskIndex = source.indexOf("`${PDA}/tasks?loadingPlanId=${encodeURIComponent(planId)}&warehouseId=${encodeURIComponent(warehouseId)}`", taskIndex + 1)
assert.ok(transportIndex >= 0 && releaseIndex > transportIndex && pushIndex > releaseIndex
&& taskIndex > pushIndex && confirmIndex > taskIndex && canonicalTaskIndex > confirmIndex)
2026-08-14 10:09:46 +08:00
assert.match(source, /containerNo: realContainerNo/)
2026-08-14 22:59:10 +08:00
assert.match(source, /const transportStable = transported\?\.containerNo === realContainerNo/)
assert.match(source, /transported\?\.cabinetNo === simulation\.cabinetNo/)
assert.match(source, /if \(!transportStable\) return null/)
2026-08-15 17:40:01 +08:00
assert.match(source, /计划确认前不生成 PDA 任务/)
assert.match(source, /summary\?\.readiness\?\.canConfirm === true/)
assert.match(source, /Object\.freeze\(\{ expectedVersion: confirmableVersion \}\)/)
assert.match(source, /confirmationStatus === 'CONFIRMED'/)
assert.match(source, /readiness\?\.readyForPda === true/)
assert.match(source, /taskId === confirmedTaskId/)
assert.match(source, /计划确认同键同参精确重放/)
assert.doesNotMatch(source, /\/preload\/confirming|\/preload\/confirm\?tenantId=/)
2026-08-14 10:09:46 +08:00
assert.match(source, /id\(task\?\.loadingPlanId\) === planId/)
assert.match(source, /id\(task\?\.containerBookingId\) === containerId/)
assert.doesNotMatch(source, /\$\{PDA\}\/tasks\?containerNo=/)
})
2026-08-14 10:57:05 +08:00
test('each remainder settles only through inventory snapshot, atomic batch and explicit complete', () => {
const claimIndex = source.indexOf("`${PDA}/tasks/${taskId}/claim`")
const snapshotIndex = source.indexOf('inventory-snapshot?warehouseId=')
const batchIndex = source.indexOf('const batchPath = `${PDA}/tasks/${taskId}/batch`')
const completeIndex = source.indexOf('const completePath = `${PDA}/tasks/${taskId}/complete`')
const treeIndex = source.indexOf('`回查 ${sourceOrderNo} 拆分后的根聚合`')
assert.ok(claimIndex >= 0 && snapshotIndex > claimIndex && batchIndex > snapshotIndex
&& completeIndex > batchIndex && treeIndex > completeIndex)
assert.match(source, /loadingPlanId: planId, containerBookingId: containerId/)
assert.match(source, /expectedVersion: Number\(inventory\.taskVersion\)/)
assert.match(source, /inventorySnapshotToken: inventory\.snapshotToken/)
assert.match(source, /action: 'PICK', pickedPackageCount: 1/)
assert.match(source, /version: Number\(claimedItem\.version\), releaseMode: 'MANUAL', locationIds: \[\]/)
assert.match(source, /'Idempotency-Key': batchKey/)
assert.match(source, /differentReplay/)
assert.match(source, /workflowStatus === 'AWAITING_REVIEW'/)
assert.match(source, /expectedTaskVersion: Number\(picked\?\.version\)/)
assert.match(source, /operationId: `\$\{operationBase\}-complete`/)
assert.match(source, /completeReplay/)
assert.match(source, /workflowStatus === 'COMPLETED'/)
assert.doesNotMatch(source, /\/items\/\$\{[^}]+\}\/pick/)
})
2026-08-14 10:09:46 +08:00
test('booking freezes the source customer channel and warehouse scope', () => {
assert.match(source, /customerId: source\.tenantId/)
assert.match(source, /channelCode: source\.channelCode/)
assert.match(source, /warehouseId: source\.warehouseId/)
2026-08-15 11:51:15 +08:00
assert.match(source, /containerType === '40HQ'/)
2026-08-15 12:12:34 +08:00
assert.match(source, /containerType: '40HQ', capacityCbm: 68, maxWeightKg: 28000/)
2026-08-15 11:51:15 +08:00
})
test('booking fixture never invents supplier names without formal IDs', () => {
assert.match(source, /oceanBookingDraft/)
assert.doesNotMatch(source, /SPLIT-SHIPPING|SPLIT-AGENT/)
assert.doesNotMatch(source, /shippingCompanyName: `|bookingAgentName: `/)
2026-08-14 10:09:46 +08:00
})