test: sequence pickup readiness before overseas receiving

This commit is contained in:
2026-08-25 10:48:10 +08:00
parent a6be840588
commit 2a16c91c89
4 changed files with 86 additions and 6 deletions
@@ -100,7 +100,7 @@ function blocker(context, name, evidence, response = null) {
context.addStep(MODULE, name, response, false, evidence, OPTIONS)
}
export async function run(context) {
export async function runUntilPickupReady(context) {
const carriersResponse = await context.request('GET', `${BASE}/carriers`)
const carriers = context.apiSuccess(MODULE, '查询船司只读辅助信息', carriersResponse, '', OPTIONS)
context.assert(MODULE, '船司辅助信息结构稳定', carriersResponse,
@@ -188,7 +188,8 @@ export async function run(context) {
const frozenLatestActualMs = frozenLatestActual == null ? Number.NaN
: Date.parse(`${frozenLatestActual}Z`)
const baseActual = (Number.isNaN(frozenLatestActualMs) ? Date.now() : frozenLatestActualMs) + 60 * 60_000
for (let index = 0; index < STANDARD_NODES.length; index += 1) {
const pickupReadyIndex = STANDARD_NODES.indexOf('PICKUP_READY')
for (let index = 0; index <= pickupReadyIndex; index += 1) {
const standardNode = STANDARD_NODES[index]
const actualAt = localIso(new Date(baseActual + index * 60_000))
const body = manualNodePayload({ expectedVersion: version, standardNode,
@@ -214,6 +215,67 @@ export async function run(context) {
detail = updated
}
const pickupTask = detail.query.query
const actualNodes = new Set(detail.nodes.filter((item) => item.actualAt != null).map((item) => item.standardNode))
const expectedPickupNodes = STANDARD_NODES.slice(0, pickupReadyIndex + 1)
context.assert(MODULE, 'PDA开始前已覆盖至PICKUP_READY且任务尚未停止', detailResponse,
expectedPickupNodes.every((node) => actualNodes.has(node)) && pickupTask.status !== 'STOPPED'
&& pickupTask.latestActualNode === 'PICKUP_READY',
`actualNodes=${[...actualNodes].join(',')}, status=${pickupTask.status}`, OPTIONS)
context.createdResources.phase4CarrierPickupReady = {
marker: context.marker, queryId, version: Number(pickupTask.version),
latestActualAt: pickupTask.latestActualAt, ready: true,
}
}
export async function run(context) {
let pickup = context.createdResources?.phase4CarrierPickupReady
if (pickup?.marker !== context.marker || pickup?.ready !== true) {
await runUntilPickupReady(context)
pickup = context.createdResources?.phase4CarrierPickupReady
}
if (!pickup?.queryId) return
const queryId = id(pickup.queryId)
const detailResponse = await context.request('GET', `${BASE}/${encodeURIComponent(queryId)}`)
let detail = context.apiSuccess(MODULE, 'PDA收货完成后恢复本轮船节点任务', detailResponse,
`queryId=${queryId}`, OPTIONS)
assertDetail(context, detailResponse, detail, '恢复任务详情DTO完整')
const pickupTask = detail?.query?.query
if (pickupTask?.latestActualNode !== 'PICKUP_READY' || pickupTask?.status === 'STOPPED') {
blocker(context, 'PDA收货后船节点未停留在PICKUP_READY,禁止伪造后续节点',
`latest=${pickupTask?.latestActualNode || 'missing'}, status=${pickupTask?.status || 'missing'}`, detailResponse)
return
}
let version = Number(pickupTask.version)
const frozenLatestActualMs = Date.parse(`${pickupTask.latestActualAt}Z`)
const baseActual = (Number.isNaN(frozenLatestActualMs) ? Date.now() : frozenLatestActualMs) + 60 * 60_000
const remainingNodes = STANDARD_NODES.slice(STANDARD_NODES.indexOf('PICKED_UP'))
for (let index = 0; index < remainingNodes.length; index += 1) {
const standardNode = remainingNodes[index]
const actualAt = localIso(new Date(baseActual + index * 60_000))
const body = manualNodePayload({ expectedVersion: version, standardNode,
observedAt: localIso(new Date()), actualAt, marker: context.marker,
index: STANDARD_NODES.indexOf(standardNode) + 1 })
const key = `${context.marker}-carrier-actual-${String(STANDARD_NODES.indexOf(standardNode) + 1).padStart(2, '0')}`.slice(0, 128)
const response = await context.request('POST', `${BASE}/${queryId}/manual-nodes`, body,
context.token, { 'Idempotency-Key': key })
const updated = context.apiSuccess(MODULE, `PDA完成后补录实际节点${standardNode}`, response,
`queryId=${queryId}`, OPTIONS)
assertDetail(context, response, updated, `实际节点${standardNode}响应DTO完整`)
const updatedTask = updated?.query?.query
const isEmptyReturned = standardNode === 'EMPTY_RETURNED'
context.assert(MODULE, `${standardNode}实际事实单调推进且停止语义正确`, response,
updatedTask?.latestActualNode === standardNode && updatedTask?.latestActualAt === actualAt
&& (isEmptyReturned
? updatedTask.status === 'STOPPED' && updatedTask.stopReason === '已还柜' && updatedTask.stoppedAt != null
: updatedTask.status !== 'STOPPED'),
`status=${updatedTask?.status || 'missing'}, latest=${updatedTask?.latestActualNode || 'none'}`, OPTIONS)
version = Number(updatedTask.version)
detail = updated
}
const finalTask = detail.query.query
const actualNodes = new Set(detail.nodes.filter((item) => item.actualAt != null).map((item) => item.standardNode))
context.assert(MODULE, '最终详情覆盖十标准实际节点且只有EMPTY_RETURNED触发停止', detailResponse,