Files
baiyue-firstleg/scripts/admin-test-env-modules/dispatch-workflow-extension-contract.test.mjs
T

104 lines
8.3 KiB
JavaScript
Raw Normal View History

2026-10-20 15:48:38 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { applyIndependentFees, feeCents, assertDualDestinationFixture, extensionMatrix, verifyLoadingReplayRace, assertBolPages } from './dispatch-workflow-extension-acceptance.mjs'
import { parseExtensionArguments, runDispatchExtensions } from '../dispatch-workflow-extensions-test-env.mjs'
test('default plan imports without credentials/network and keeps all unexecuted gaps explicit', async () => {
const plan = await runDispatchExtensions(parseExtensionArguments([]), {})
assert.equal(plan.execute, false); assert.equal(plan.matrix.length, 5)
assert.equal(extensionMatrix.find(row => row.scenario === 'relabel').readiness, 'REQUIRES_FORMAL_RELABEL_FIXTURE')
})
test('execution requires fresh explicit TEST scope and declared synthetic load, never coerces a plan', () => {
const args = ['--execute', '--environment', 'TEST', '--run-id', 'EXT1', '--scenario', 'dual-destination', '--warehouse-id', '10',
'--destination-warehouse-id', '20', '--pda-env-file', 'private.env', '--synthetic-pallet-spaces', '1']
assert.equal(parseExtensionArguments(args).execute, true)
for (const [from, to] of [['TEST', 'DEV'], ['EXT1', 'plan'], ['dual-destination', 'relabel'], ['10', ''], ['1', '0']])
assert.throws(() => parseExtensionArguments(args.map(value => value === from ? to : value)))
assert.throws(() => parseExtensionArguments([...args, '--execute']))
})
test('terminal continuation requires an explicit original report hash and is never an implicit replay', () => {
assert.throws(() => parseExtensionArguments(['--scenario', 'review-terminal']))
assert.throws(() => parseExtensionArguments(['--resume-report', 'reports/old/report.json']))
const config = parseExtensionArguments(['--execute', '--environment', 'TEST', '--run-id', 'EXTCLOSE1', '--scenario', 'review-terminal',
'--warehouse-id', '10', '--destination-warehouse-id', '20', '--pda-env-file', 'private.env', '--synthetic-pallet-spaces', '1',
'--resume-report', 'reports/current/report.json', '--resume-report-sha256', 'a'.repeat(64)])
assert.equal(config.scenario, 'review-terminal'); assert.equal(config.resumeReportSha256, 'a'.repeat(64))
})
test('actual group owner controls distinct cents even when server group ordering differs', () => {
const groups = [{ groupKey: 'B-GROUP', customerId: 'B' }, { groupKey: 'A-GROUP', customerId: 'A' }]
const body = { recordVersion: '9007199254740993', idempotencyKey: 'ORIGINAL', groups: groups.map(row => ({ groupKey: row.groupKey,
fees: [{ feeType: 'DELIVERY_FEE', currency: 'CNY', amount: 30 }] })) }
const expected = applyIndependentFees(body, groups, [{ customerId: 'A' }, { customerId: 'B' }], 'CURRENT')
assert.deepEqual(body.groups.map(row => row.fees[0].amount), [47.75, 31.25])
assert.equal(expected.A[0].cents, 3125); assert.equal(expected.B[0].cents, 4775)
assert.equal(body.recordVersion, '9007199254740993'); assert.equal(body.idempotencyKey, 'ORIGINAL')
assert(body.groups.every(row => row.fees[0].feeName.includes('TEST合成')))
})
test('unknown customers, repeated groups and missing real required fees fail before transport', () => {
const members = [{ customerId: 'A' }, { customerId: 'B' }]
for (const groups of [[{ groupKey: 'G', customerId: 'A' }, { groupKey: 'G', customerId: 'B' }],
[{ groupKey: 'A', customerId: 'A' }, { groupKey: 'X', customerId: 'FOREIGN' }]]) {
const body = { groups: groups.map(row => ({ groupKey: row.groupKey, fees: [{ feeType: 'DELIVERY_FEE', currency: 'CNY' }] })) }
assert.throws(() => applyIndependentFees(body, groups, members, 'CURRENT'))
}
assert.throws(() => applyIndependentFees({ groups: [{ groupKey: 'A', fees: [] }, { groupKey: 'B', fees: [] }] },
[{ groupKey: 'A', customerId: 'A' }, { groupKey: 'B', customerId: 'B' }], members, 'CURRENT'))
})
test('dual destination proof rejects reused customer, collapsed destination and missing route stop', () => {
const fixture = { members: [{ customerId: 'A', destination: { key: 'FBA:ONE' }, receiptVerified: true },
{ customerId: 'B', destination: { key: 'FBA:TWO' }, receiptVerified: true }],
tripProof: { stops: [{}, { destinationKey: 'FBA:ONE' }, { destinationKey: 'FBA:TWO' }] } }
assert.doesNotThrow(() => assertDualDestinationFixture(fixture))
for (const mutate of [f => { f.members[1].customerId = 'A' }, f => { f.members[1].destination.key = 'FBA:ONE' },
f => { f.tripProof.stops.pop() }]) { const bad = structuredClone(fixture); mutate(bad); assert.throws(() => assertDualDestinationFixture(bad)) }
})
test('fee conservation uses integer cents and rejects unsafe, negative or overprecision amounts', () => {
assert.equal(feeCents('31.25'), 3125); assert.equal(feeCents(47.75), 4775)
for (const value of [null, false, -1, '1e2', '0.001', Number.MAX_SAFE_INTEGER]) assert.throws(() => feeCents(value))
})
for (const unknown of [false, true]) test(`bounded two-actor race ${unknown ? 'preserves unknown result and stops' : 'starts both requests before either returns and conserves inventory'}`, async () => {
const fixture = { dispatchNo: 'OWN', tripNo: 'TRIP', warehouseId: 'W', pdaToken: 'OWN_PDA_TOKEN', members: [{ customerId: 'A' }, { customerId: 'B' }] }
const context = { marker: 'CURRENT', createdResources: { dispatchWorkflowCompetition: { status: 'CHECKING' } },
checkpoint: async () => {}, assert: () => {} }
const acknowledgement = { recordVersion: '8', result: { confirmedLoad: { pallets: ['P1', 'P2'] } } }
const body = { recordVersion: '7', idempotencyKey: 'ORIGINAL', measurements: ['P1', 'P2'] }, requests = [], waiting = []
const send = async (method, path, payload, token) => {
requests.push({ method, path, payload: structuredClone(payload), token })
if (method === 'GET') return { http: 200, body: { code: '200', data: { immutable: path } } }
return new Promise((resolve, reject) => {
waiting.push({ resolve, reject, token })
if (waiting.length === 2) for (const entry of waiting) {
if (entry.token) entry.resolve({ http: 200, body: { code: '200', data: { ...acknowledgement, replayed: true } } })
else if (unknown) entry.reject(Error('Unknown write outcome; preserve original key'))
else entry.resolve({ http: 200, body: { code: 'A0403', message: '该请求标识不属于当前操作员' } })
}
})
}
const execute = () => verifyLoadingReplayRace(context, fixture, { readInventory: async scope => ({ ...scope, available: 0, picked: 1 }) }, send, body, acknowledgement)
if (unknown) await assert.rejects(execute(), /Unknown write outcome/)
else await execute()
assert.equal(waiting.length, 2)
assert.equal(requests.filter(row => row.method === 'POST').length, 2)
assert(requests.filter(row => row.method === 'POST').every(row => JSON.stringify(row.payload) === JSON.stringify(body)))
const proof = context.createdResources.dispatchWorkflowCompetition.loadingReplayRace
assert.equal(proof.status, unknown ? 'UNKNOWN_RESULT' : 'VERIFIED')
assert.equal(proof.firstExecutionRace, 'NOT_TESTED')
if (!unknown) assert.deepEqual(proof.before, proof.after)
})
test('BOL PDF evidence enforces empty/same/different appointment grouping and actual quantity conservation', () => {
const fixture = { members: [{ palletNo: 'PALLET_A', incomingNo: 'PALLET_A' }, { palletNo: 'PALLET_B', incomingNo: 'PALLET_B' }] }
const pdf = (isa, pallets, total) => `BILL OF LADING\nISA #: ${isa}\n${pallets.join('\n')}\n总箱数 / Total CTNS:\n${total}\nDriver signature`
assert.equal(assertBolPages([pdf('', ['PALLET_A', 'PALLET_B'], 2)], fixture, { appointments: [] }, 'bol-empty').totalPackages, 2)
assert.equal(assertBolPages([pdf('ISA1', ['PALLET_A', 'PALLET_B'], 2)], fixture,
{ appointments: [{ data: { confirmationNo: 'ISA1' } }] }, 'bol-same').pageCount, 1)
const appointments = { appointments: [{ data: { confirmationNo: 'ISA1' } }, { data: { confirmationNo: 'ISA2' } }] }
const pages = [pdf('ISA1', ['PALLET_A'], 1), pdf('ISA2', ['PALLET_B'], 1)]
assert.equal(assertBolPages(pages, fixture, appointments, 'bol-different').pageCount, 2)
for (const bad of [[pdf('ISA1', ['PALLET_A'], 2), pdf('ISA2', ['PALLET_B'], 2)],
[pdf('ISA1', ['PALLET_A', 'PALLET_B'], 1), pdf('ISA2', ['PALLET_B'], 1)],
[pdf('ISA1', ['PALLET_A'], 1), pdf('ISA1', ['PALLET_B'], 1)]])
assert.throws(() => assertBolPages(bad, fixture, appointments, 'bol-different'))
})