Files
baiyue-firstleg/scripts/admin-test-env-modules/phase5-terminal-drayage-contract.test.mjs
T
2026-09-16 11:06:33 +08:00

120 lines
6.5 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import test from 'node:test'
import { fileURLToPath } from 'node:url'
import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs'
import { prepareTerminalDrayage, run } from './phase5-terminal-drayage.mjs'
const source = await readFile(fileURLToPath(new URL('./phase5-terminal-drayage.mjs', import.meta.url)), 'utf8')
test('three real command payloads mutate contract fields and preserve remarks and state on conflict', async () => {
const marker = 'offline-terminal'
const containerBookingId = '9223372036854775806'
const containerNo = 'TEST0000001'
const actions = ['SELECT_FLEET', 'SCHEDULE_PICKUP', 'RECORD_EMPTY_RETURN']
const paths = ['fleet', 'pickup-appointment', 'empty-return']
let state = { containerBookingId, containerNo, version: 0, allowedActions: ['SELECT_FLEET'] }
const history = []
const commands = new Map()
let conflicts = 0
let replays = 0
const response = (data, code = '200') => ({ http: 200, body: { code, data }, requestId: 'offline-request', traceId: 'offline-trace' })
const producer = { containerBookingId, containerNo, shippingReady: true,
carrierScheduleQueryId: '9223372036854775805', carrierLatestActualAt: '2026-09-09T01:00:00' }
const context = {
marker, token: 'offline-only',
createdResources: {
phase4InboundProducerFixtures: { marker, containers: [producer] },
phase4OverseasPda: { marker, finalStatus: 'COMPLETED', completedContainerFacts: [{ containerBookingId, containerNo }] },
},
apiSuccess(_module, name, result) { assert.equal(result.body.code, '200', name); return result.body.data },
assert(_module, name, _response, passed) { assert.ok(passed, name) },
addStep(_module, name, _response, passed) { assert.ok(passed, name) },
addCoverageGap() { assert.fail('unexpected missing fixture') },
async request(method, path, body, _token, headers) {
if (path.includes('/suppliers?')) return response({ records: [{ id: '9223372036854775804', name: 'Offline fleet', category: 'TRUCKING', truckingScope: 'OVERSEAS', status: 'ACTIVE' }] })
if (method === 'GET') {
if (path.endsWith('/history')) return response(history)
if (path.includes('?containerBookingIds=')) return response([state])
return response(state)
}
const actionPath = path.split('/').at(-1)
const index = paths.indexOf(actionPath)
assert.ok(index >= 0)
const field = index < 2 ? 'reason' : 'remark'
const forbidden = index < 2 ? 'remark' : 'reason'
assert.equal(Object.hasOwn(body, forbidden), false, `${actionPath} must not send ${forbidden}`)
const key = headers['Idempotency-Key']
assert.match(key, /^[A-Za-z0-9:_-]+$/)
assert.ok(key.endsWith(`terminal-${actionPath}`))
const saved = commands.get(key)
if (saved) {
if (JSON.stringify(saved.body) === JSON.stringify(body)) { replays++; return response(saved.state) }
assert.deepEqual(body, { ...saved.body, [field]: `${marker}-DIFFERENT` })
conflicts++
return response(null, 'A0409')
}
assert.equal(body[field], marker)
assert.equal(body.expectedVersion, state.version)
state = { ...state, version: state.version + 1 }
if (index === 0) Object.assign(state, { fleetSupplierId: body.fleetSupplierId, fleetSupplierName: body.fleetSupplierName, allowedActions: ['SCHEDULE_PICKUP'] })
if (index === 1) state.pickupAppointmentAt = body.pickupAppointmentAt
if (index === 2) Object.assign(state, { status: 'EMPTY_RETURNED', actualEmptyReturnAt: body.actualAt, allowedActions: [], emptyReturnWarning: { status: 'RESOLVED' } })
history.push({ action: actions[index], fromVersion: index, toVersion: index + 1, reason: body[field] })
commands.set(key, { body: structuredClone(body), state: structuredClone(state) })
return response(state)
},
}
await prepareTerminalDrayage(context, producer)
state = { ...state, status: 'UNLOADED', unloadingCompletedAt: '2026-09-09T04:00:00', actualPickupAt: '2026-09-09T04:00:00', allowedActions: ['RECORD_EMPTY_RETURN'] }
await run(context)
assert.equal(commands.size, 3)
assert.equal(replays, 3)
assert.equal(conflicts, 3)
assert.equal(state.version, 3)
assert.equal(context.createdResources.phase5TerminalDrayage.finalStatus, 'EMPTY_RETURNED')
})
test('terminal drayage prepares pickup before unloading and finalizes return before carrier validation', () => {
const modules = normalizeModuleOrder(['phase4-carrier-nodes'])
const pickup = modules.indexOf('phase4-carrier-pickup-ready')
const pda = modules.indexOf('phase4-overseas-pda')
const drayage = modules.indexOf('phase5-terminal-drayage')
const finalCarrier = modules.indexOf('phase4-carrier-nodes')
assert.ok(pickup >= 0 && pickup < pda)
assert.ok(pda < drayage && drayage < finalCarrier)
assert.match(source, /attempt <= 30/)
assert.match(source, /initialData\?\.status === 'UNLOADED'/)
})
test('module uses only public business endpoints and reuses public trucking master data', () => {
for (const path of [
'/api/platform/v1/terminal-drayages', "'fleet'", "'pickup-appointment'", "'empty-return'", '/history',
'category=TRUCKING',
]) assert.ok(source.includes(path), `missing ${path}`)
assert.doesNotMatch(source, /\/internal\//)
assert.doesNotMatch(source, /POST[^\n]*\/api\/platform\/v1\/suppliers/)
assert.match(source, /脚本不临时创建主数据/)
})
test('all three commands cover idempotent replay hash conflict and lifecycle semantics', () => {
for (const token of [
'选择车队', '预约提柜', '确认实际还柜',
'同键重放', '同键异载荷返回A0409', 'UNLOADED', 'EMPTY_RETURNED',
'SELECT_FLEET', 'SCHEDULE_PICKUP', 'RECORD_EMPTY_RETURN', 'RESOLVED',
]) assert.ok(source.includes(token), `missing ${token}`)
assert.match(source, /JSON\.stringify\(replay\.data\) === JSON\.stringify\(first\.data\)/)
assert.match(source, /terminal-\$\{path\}.*replace\(\/\[\^A-Za-z0-9:_-\]\//)
assert.doesNotMatch(source, /terminal-\$\{action\}/)
})
test('target is strictly current-marker pickup and completed-PDA container', () => {
assert.match(source, /matrix\?\.marker !== context\.marker/)
assert.match(source, /prepared\?\.marker !== context\.marker/)
assert.match(source, /pda\?\.finalStatus !== 'COMPLETED'/)
assert.match(source, /completedContainerFacts/)
assert.match(source, /producer\?\.shippingReady !== true/)
assert.match(source, /phase4CarrierPickupReady =/)
assert.match(source, /禁止借历史货柜/)
})