test: stabilize full dev acceptance orchestration

This commit is contained in:
2026-09-09 20:36:53 +08:00
parent 71604c9c39
commit eac6875877
16 changed files with 104 additions and 85 deletions
@@ -19,37 +19,6 @@ function operationId(context, label) {
return `${context.marker}-manual-loading-${label}-${crypto.randomUUID()}`.slice(0, 128)
}
async function post(context, path, body, label) {
return context.request('POST', path, body, context.token,
{ 'Idempotency-Key': operationId(context, label) })
}
async function uploadDocument(context, path, expectedVersion, operationReason, label) {
const form = new FormData()
form.append('file', new Blob([`${context.marker}-${label}`], { type: 'text/plain' }), `${label}.txt`)
form.append('expectedVersion', String(expectedVersion))
if (operationReason) form.append('operationReason', operationReason)
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method: 'POST',
headers: {
Accept: 'application/json', Authorization: `Bearer ${context.token}`,
'Idempotency-Key': operationId(context, `${label}-upload`),
},
body: form,
signal: AbortSignal.timeout(60_000),
})
const raw = await response.text()
let body = null
try { body = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
return { method: 'POST', path, http: response.status, body, raw: body ? null : raw.slice(0, 500),
requestId: body?.requestId || response.headers.get('x-request-id'),
traceId: body?.traceId || response.headers.get('x-trace-id') }
} catch (error) {
return { method: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
}
}
async function ensureCompletionEvidence(context, chain, label) {
const taskId = id(chain.task?.id)
const warehouseId = id(chain.source?.warehouseId)
@@ -351,34 +320,26 @@ function fixture(value, status, detailPath, extra = {}) {
return { id: id(value), status, detailPath, ...extra }
}
async function completeSimpleDocuments(context, chain, matrix) {
const reason = `${context.marker} 人工页面动作验收`
const clearancePath = `${CONTAINERS}/${chain.containerId}/clearance`
const clearanceUpload = await uploadDocument(context, `${clearancePath}/document/upload`, 0,
reason, 'container-clearance')
const clearanceDocument = context.apiSuccess(MODULE, '货柜公开接口上传目的港清关资料',
clearanceUpload, '', OPTIONS)
context.assert(MODULE, '货柜清关上传返回真实文件事实', clearanceUpload,
Boolean(clearanceDocument?.id) && Boolean(clearanceDocument?.fileId)
&& clearanceDocument?.documentType === 'DESTINATION_CLEARANCE'
&& /^[0-9a-f]{64}$/i.test(String(clearanceDocument?.fileHash || ''))
&& !clearanceDocument?.completedAt,
`documentId=${id(clearanceDocument?.id) || 'missing'}, version=${clearanceDocument?.version ?? 'missing'}`, OPTIONS)
if (!clearanceDocument?.id || !clearanceDocument?.fileId || !Number.isInteger(Number(clearanceDocument?.version))) return
const clearanceCompleteResponse = await post(context, `${clearancePath}/complete`, {
expectedVersion: Number(clearanceDocument?.version), operationReason: reason,
}, 'container-clearance-complete')
const clearanceCompleted = context.apiSuccess(MODULE, '货柜点击完成目的港清关',
clearanceCompleteResponse, '', OPTIONS)
context.assert(MODULE, '货柜清关完成事实可回读', clearanceCompleteResponse,
id(clearanceCompleted?.id) === id(clearanceDocument?.id) && Boolean(clearanceCompleted?.completedAt)
&& Number(clearanceCompleted?.version) === Number(clearanceDocument?.version) + 1,
`documentId=${id(clearanceCompleted?.id) || 'missing'}, completedAt=${clearanceCompleted?.completedAt || 'missing'}`, OPTIONS)
async function registerClearanceAction(context, matrix) {
const documentsFixture = context.createdResources?.phase3ContainerDocuments
const containerId = id(documentsFixture?.containerBookingId)
const expectedDocumentId = id(documentsFixture?.documentIds?.DESTINATION_CLEARANCE)
context.assert(MODULE, '复用本轮离仓后已完成的目的港清关动作', null,
documentsFixture?.marker === context.marker && Boolean(containerId && expectedDocumentId),
`containerBookingId=${containerId || 'missing'}, documentId=${expectedDocumentId || 'missing'}`, OPTIONS)
if (!containerId || !expectedDocumentId || documentsFixture?.marker !== context.marker) return
matrix.pages.containerLoading.actions = { clearance: fixture(chain.containerId, 'COMPLETED',
`/container-loading/${chain.containerId}/smart-loading`, {
documentId: id(clearanceCompleted?.id), documentVersion: Number(clearanceCompleted?.version),
completedAt: clearanceCompleted?.completedAt || null,
const response = await context.request('GET', `${CONTAINERS}/${containerId}/clearance`)
const completed = context.apiSuccess(MODULE, '回读目的港清关动作验收事实', response, '', OPTIONS)
context.assert(MODULE, '目的港清关动作保持完成和版本事实', response,
id(completed?.id) === expectedDocumentId && completed?.documentType === 'DESTINATION_CLEARANCE'
&& Boolean(completed?.completedAt) && Number.isInteger(Number(completed?.version)),
`documentId=${id(completed?.id) || 'missing'}, completedAt=${completed?.completedAt || 'missing'}, version=${completed?.version ?? 'missing'}`, OPTIONS)
matrix.pages.containerLoading.actions = { clearance: fixture(containerId, 'COMPLETED',
`/container-loading/${containerId}/smart-loading`, {
documentId: id(completed?.id), documentVersion: Number(completed?.version),
completedAt: completed?.completedAt || null,
}) }
}
@@ -464,6 +425,6 @@ export async function run(context, matrix = context.createdResources.manualState
id(listDetail?.summary?.planId) === listFixture.id && listDetail?.summary?.status === 'COMPLETED',
`planId=${listFixture.id}, actual=${listDetail?.summary?.status || 'missing'}`, OPTIONS)
await completeSimpleDocuments(context, completed, matrix)
await registerClearanceAction(context, matrix)
}
import { iso6346ContainerNumber } from './iso6346-container-number.mjs'