2026-09-14 12:00:48 +08:00
|
|
|
import test from 'node:test'
|
|
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import { readFile } from 'node:fs/promises'
|
|
|
|
|
import { parseArguments } from '../admin-test-env-acceptance.mjs'
|
|
|
|
|
import { verifyWarehouseTransferReview } from './warehouse-transfer-review.mjs'
|
|
|
|
|
|
|
|
|
|
const read = name => readFile(new URL(name, import.meta.url), 'utf8')
|
|
|
|
|
test('merged scenarios remain in default and focused dependency chains', () => {
|
|
|
|
|
const defaults = parseArguments([]).modules
|
|
|
|
|
for (const module of ['phase4-overseas-client-services', 'phase4-overseas-dispatch', 'phase5-overseas-fulfillment']) {
|
|
|
|
|
assert.ok(defaults.includes(module))
|
|
|
|
|
const focused = parseArguments(['--modules', module]).modules
|
|
|
|
|
assert.ok(focused.indexOf('merchant-qualification-fixture') >= 0)
|
|
|
|
|
assert.ok(focused.indexOf('phase4-overseas-pda-operations') < focused.indexOf(module))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('review helper refuses stale fixture before any request', async () => {
|
|
|
|
|
const steps = []
|
|
|
|
|
await verifyWarehouseTransferReview({ marker: 'current', context: { merchantQualificationFixture: { marker: 'old' } },
|
2026-09-29 19:48:34 +08:00
|
|
|
addStep: (...args) => steps.push(args), request: () => { throw Error('must not request') } }, 'token', '123', {}, 'T1')
|
2026-09-14 12:00:48 +08:00
|
|
|
assert.equal(steps.length, 1)
|
|
|
|
|
assert.equal(steps[0][3], false)
|
|
|
|
|
assert.equal(steps[0][5].deploymentBlocker, true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('transfer review integration covers both source types and known blank-reason failure', async () => {
|
|
|
|
|
const source = await read('warehouse-transfer-review.mjs')
|
|
|
|
|
const caller = await read('phase4-overseas-client-services.mjs')
|
2026-10-22 15:41:50 +08:00
|
|
|
assert.match(caller, /if \(!await verifyWarehouseTransferReview\(context, token, order.row.customerId, body, transferNo\)\) return false/)
|
|
|
|
|
assert.ok(caller.indexOf('await allocateWarehouseTransfer(') > caller.indexOf('if (!await verifyWarehouseTransferReview('))
|
|
|
|
|
assert.ok(caller.indexOf('await receiveWarehouseTransfer(') > caller.indexOf('await outboundWarehouseTransfer('))
|
2026-09-29 15:06:42 +08:00
|
|
|
for (const value of ['WAIT_PDA_RECEIVING', 'INBOUND_MEASURING', 'CANCELLED',
|
2026-09-14 12:00:48 +08:00
|
|
|
'REJECTED', 'APPROVED', 'customerId=', 'rejected(denied, 403)', 'rejected(missingScope, 400)',
|
|
|
|
|
'rejected(reverse, 409)', 'rejected(emptyReason, 400)', "sourceLastlegOrderIds: []"])
|
|
|
|
|
assert.ok(source.includes(value), value)
|
|
|
|
|
assert.doesNotMatch(source, /fetch\(|mysql|process\.env|\/internal\//)
|
|
|
|
|
const controller = await read('../../lastleg-service/src/main/java/com/baiyue/lastleg/transfer/controller/WarehouseTransferController.java')
|
|
|
|
|
assert.match(controller, /WAREHOUSE_TRANSFER_APPROVE/)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('draft and removal checks use actual IDs plus readback rather than snapshot-only success', async () => {
|
|
|
|
|
const source = await read('phase4-overseas-dispatch.mjs')
|
2026-10-20 10:35:52 +08:00
|
|
|
for (const value of ['drafts/containers', 'draftContainerId: temporary', 'draft.containers',
|
|
|
|
|
'draft-locks/unlock', 'drafts/pre-arrange', 'item.dispatchItemId', 'current.data.counts.palletCount',
|
|
|
|
|
'available.some(c => c.sourceKey === duplicate.sourceKey', 'dispatchBusinessSnapshot(await readDispatch'])
|
2026-09-14 12:00:48 +08:00
|
|
|
assert.ok(source.includes(value), value)
|
|
|
|
|
})
|
|
|
|
|
|
2026-10-22 11:56:47 +08:00
|
|
|
test('fulfillment checks PDF signature, automatic documents and immutable stock around loading replay', async () => {
|
2026-09-14 12:00:48 +08:00
|
|
|
const source = await read('phase5-overseas-fulfillment.mjs')
|
2026-10-22 11:56:47 +08:00
|
|
|
for (const value of ['requestBinary', '/prints/bol', "=== '%PDF-'", 'await readAutomaticDispatchDelivery(',
|
|
|
|
|
'value?.documents?.deliveries', 'value?.documents?.outbounds', 'await verifyAutomaticDeparture(',
|
2026-10-20 10:35:52 +08:00
|
|
|
'sameSnapshot(proof.stockAfter, proof.stockAfterDepart)', 'sameSnapshot(proof.ledgerAfter, proof.ledgerAfterDepart)'])
|
2026-09-14 12:00:48 +08:00
|
|
|
assert.ok(source.includes(value), value)
|
2026-10-22 11:56:47 +08:00
|
|
|
assert.doesNotMatch(source, /\/transfer-delivery|\/delivery-arrangement|\/depart`/)
|
2026-10-20 10:35:52 +08:00
|
|
|
const helper = await read('fleet-trip-fulfillment.mjs')
|
|
|
|
|
for (const value of ['state.itemsBefore', 'JSON.stringify(state.itemsBefore) === JSON.stringify(state.itemsAfter)',
|
|
|
|
|
'proof.loadingCommand.replay = replay', 'proof.loadingCommand.platformRejection = await command',
|
|
|
|
|
"{ code: 'A0403', message: '该请求标识不属于当前操作员' }", 'JSON.stringify(replay.result) === JSON.stringify(confirmed.result)',
|
|
|
|
|
'JSON.stringify(proof.loadingCommand.beforeReplay) === JSON.stringify(proof.loadingCommand.afterReplay)'])
|
|
|
|
|
assert.ok(helper.includes(value), value)
|
2026-09-14 12:00:48 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('return merge sources are actual consumed batches, not a synthetic MERGED identifier', async () => {
|
|
|
|
|
const source = await read('phase4-overseas-return-receipt.mjs')
|
|
|
|
|
assert.doesNotMatch(source, /sourceBatchNo === 'MERGED'/)
|
|
|
|
|
for (const value of ['consumedBatches', 'new Set(actualBatches)', 'sourceReturnNo === returnNo',
|
|
|
|
|
'客户箱号管理按实物箱分页', '标签包含SVG条码和当前箱号']) assert.ok(source.includes(value), value)
|
|
|
|
|
})
|