test: cover partial and concurrent pda completion

This commit is contained in:
2026-08-25 09:58:59 +08:00
parent b67217332a
commit cb78bc6b41
4 changed files with 81 additions and 7 deletions
@@ -129,13 +129,50 @@ export async function run(context) {
packageCount: Number(item?.packageCount), picked: Number(item?.pickedPackageCount) })),
})
const operationsBeforeSignature = JSON.stringify(operationsBefore || [])
const completeBody = {
expectedTaskVersion: Number(taskBefore.version),
operationId: `${context.marker}-pda-final-complete`.slice(0, 128), remark: context.marker,
}
const completeKey = `${context.marker}-p3-complete-after-downstream`.slice(0, 128)
const completeResponse = await request(context, 'POST', `${taskPath}/complete`, completeBody,
{ 'Idempotency-Key': completeKey })
const completeAttempts = [1, 2].map((index) => ({
body: {
expectedTaskVersion: Number(taskBefore.version),
operationId: `${context.marker}-pda-final-complete-concurrent-${index}`.slice(0, 128),
remark: context.marker,
},
key: `${context.marker}-p3-complete-concurrent-key-${index}`.slice(0, 128),
}))
const concurrentCompleteResponses = await Promise.all(completeAttempts.map((attempt) =>
request(context, 'POST', `${taskPath}/complete`, attempt.body,
{ 'Idempotency-Key': attempt.key })))
const successfulCompleteIndexes = concurrentCompleteResponses.flatMap((response, index) =>
response.http === 200 && codeOf(response) === '200' ? [index] : [])
const conflictedCompleteIndexes = concurrentCompleteResponses.flatMap((response, index) =>
response.http === 200 && codeOf(response) === 'A0409' ? [index] : [])
const conflictedCompleteResponse = conflictedCompleteIndexes.length === 1
? concurrentCompleteResponses[conflictedCompleteIndexes[0]] : null
const winningCompleteIndex = successfulCompleteIndexes[0] ?? -1
const completeResponse = winningCompleteIndex >= 0
? concurrentCompleteResponses[winningCompleteIndex] : concurrentCompleteResponses[0]
const completeBody = winningCompleteIndex >= 0
? completeAttempts[winningCompleteIndex].body : completeAttempts[0].body
const completeKey = winningCompleteIndex >= 0
? completeAttempts[winningCompleteIndex].key : completeAttempts[0].key
const operationsAfterConcurrentResponse = await context.request('GET', `${taskPath}/operations`)
const operationsAfterConcurrent = context.apiSuccess(MODULE,
'并发完成后回查 PDA 操作审计', operationsAfterConcurrentResponse, '', OPTIONS)
const baselineCompleteOperationIds = new Set((operationsBefore || [])
.filter((item) => item?.actionType === 'COMPLETE_LOADING').map((item) => String(item?.operationId || '')))
const concurrentCompleteOperations = (operationsAfterConcurrent || []).filter((item) =>
item?.actionType === 'COMPLETE_LOADING'
&& completeAttempts.some((attempt) => attempt.body.operationId === item?.operationId)
&& !baselineCompleteOperationIds.has(String(item?.operationId || '')))
const concurrentCompleteVerified = successfulCompleteIndexes.length === 1
&& conflictedCompleteIndexes.length === 1
&& Boolean(conflictedCompleteResponse?.requestId) && Boolean(conflictedCompleteResponse?.traceId)
&& Array.isArray(operationsAfterConcurrent) && Array.isArray(operationsBefore)
&& operationsAfterConcurrent.length === operationsBefore.length + 1
&& concurrentCompleteOperations.length === 1
&& concurrentCompleteOperations[0]?.operationId === completeBody.operationId
&& id(concurrentCompleteOperations[0]?.operatorId) && Boolean(concurrentCompleteOperations[0]?.createdAt)
context.assert(MODULE, '两个不同幂等键并发 complete 仅一成功且只写一条完成审计',
completeResponse, concurrentCompleteVerified,
`results=${concurrentCompleteResponses.map((response) => `${response.http}/${codeOf(response)}`).join(',')}, success=${successfulCompleteIndexes.length}, conflict=${conflictedCompleteIndexes.length}, completeAudits=${concurrentCompleteOperations.length}, winner=${completeBody.operationId}`, OPTIONS)
if (completeResponse.http !== 200 || codeOf(completeResponse) !== '200') {
context.addStep(MODULE, '结算递增 inboundVersion 后 final complete 必须使用最新版本', completeResponse, false,
`expected=success after settlement refresh; HTTP=${completeResponse.http}, code=${codeOf(completeResponse)}, message=${completeResponse.body?.message || 'missing'}, taskId=${taskId}, inboundId=${inboundId}, taskVersion=${taskBefore?.version ?? 'missing'}, inboundVersion=${version}, requestId=${completeResponse.requestId || 'missing'}, traceId=${completeResponse.traceId || 'missing'}`, OPTIONS)
@@ -166,6 +203,7 @@ export async function run(context) {
`taskUnchanged=${taskAfterSignature === taskBeforeSignature}, planUnchanged=${planAfterSignature === planBeforeSignature}, inboundStatus=${inboundAfterFailure?.summary?.processStatus || 'missing'}, inboundVersion=${inboundAfterFailure?.summary?.version ?? 'missing'}, operationsUnchanged=${JSON.stringify(operationsAfterFailure || []) === operationsBeforeSignature}`, OPTIONS)
return
}
if (!concurrentCompleteVerified) return
const completed = context.apiSuccess(MODULE, '订舱与五类证据等保留门禁齐备后完成装柜', completeResponse, '', OPTIONS)
context.assert(MODULE, '统一硬门禁通过后 task 与 workflow 同事务完成且身份不漂移', completeResponse,
completed?.taskStatus === 'COMPLETED' && completed?.workflowStatus === 'COMPLETED'