test(acceptance): recover completed-plan rejection probe after verified connection loss
This commit is contained in:
@@ -4,6 +4,41 @@ const OPTIONS = { scenario: 'phase3-loading-hard-gate-finalize', deploymentBlock
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
|
||||
/** 只对已完成计划的拒绝探针核实未知结果;原体原键最多重放一次,不用于正常业务写入。 */
|
||||
export async function completedPlanAssignmentProbe(context, { planPath, body, baseline }, send = request) {
|
||||
if (baseline?.status !== 'COMPLETED' || String(baseline.version) !== String(body.expectedVersion)
|
||||
|| !body.operationId?.startsWith(context.marker) || !body.inboundId || !body.inboundDetailId || body.packageCount !== 1)
|
||||
throw Error('已完成计划拒绝探针缺少原状态、版本及不可变命令')
|
||||
const signature = value => JSON.stringify(value, (_, row) => row && typeof row === 'object' && !Array.isArray(row)
|
||||
? Object.fromEntries(Object.keys(row).sort().map(key => [key, row[key]])) : row)
|
||||
const original = structuredClone(body)
|
||||
const evidence = { path: `${planPath}/assignments`, body: original, status: 'PREPARED', attempts: [] }
|
||||
;(context.createdResources.completedPlanAssignmentProbes ??= []).push(evidence)
|
||||
await context.checkpoint?.()
|
||||
const attempt = async () => {
|
||||
evidence.status = 'SENDING'; await context.checkpoint?.()
|
||||
const response = await send(context, 'POST', evidence.path, original)
|
||||
evidence.attempts.push(response); evidence.status = response.http === 0 ? 'UNKNOWN_RESULT' : 'RESPONDED'
|
||||
await context.checkpoint?.(); return response
|
||||
}
|
||||
const first = await attempt()
|
||||
if (first.http !== 0) return first
|
||||
const current = await context.request('GET', planPath)
|
||||
evidence.readback = current
|
||||
if (current.http !== 200 || codeOf(current) !== '200' || current.body?.data?.status !== 'COMPLETED'
|
||||
|| signature(current.body.data) !== signature(baseline)) {
|
||||
evidence.status = 'UNKNOWN_RESULT_STATE_CHANGED'; await context.checkpoint?.()
|
||||
throw Error('拒绝探针连接中断后原计划状态或明细变化,禁止重放及后续修改')
|
||||
}
|
||||
const replay = await attempt()
|
||||
const after = await context.request('GET', planPath); evidence.after = after
|
||||
const unchanged = after.http === 200 && codeOf(after) === '200' && signature(after.body.data) === signature(baseline)
|
||||
evidence.status = replay.http === 200 && codeOf(replay) === 'A0409' && unchanged ? 'VERIFIED_REJECTION_AFTER_TRANSPORT_RECOVERY' : 'UNVERIFIED'
|
||||
await context.checkpoint?.()
|
||||
if (!unchanged) throw Error('原键拒绝探针重放后计划不一致,停止后续修改')
|
||||
return replay
|
||||
}
|
||||
|
||||
export async function replayCompletedCommand(send, recordTransportFailure) {
|
||||
const first = await send()
|
||||
if (first.http !== 0) return first
|
||||
@@ -84,7 +119,8 @@ async function request(context, method, path, body, headers = {}) {
|
||||
requestId: payload?.requestId || response.headers.get('x-request-id'),
|
||||
traceId: payload?.traceId || response.headers.get('x-trace-id') }
|
||||
} catch (error) {
|
||||
return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
|
||||
return { method, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null,
|
||||
transportError: { name: error?.name, code: error?.cause?.code || error?.code, cause: error?.cause?.message } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,13 +556,12 @@ export async function run(context) {
|
||||
`HTTP=${immutableTransport.http}, code=${codeOf(immutableTransport)}, requestId=${immutableTransport.requestId || 'missing'}, traceId=${immutableTransport.traceId || 'missing'}`, OPTIONS)
|
||||
|
||||
const completedPlanItem = immutablePlan?.items?.[0]
|
||||
const assignmentAfterCompleted = await request(context, 'POST',
|
||||
`/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(loadingPlanId)}/assignments`, {
|
||||
const assignmentAfterCompleted = await completedPlanAssignmentProbe(context, { planPath, baseline: immutablePlan, body: {
|
||||
inboundId: fixture.incrementalInboundId || inboundId,
|
||||
inboundDetailId: id(completedPlanItem?.inboundDetailId), packageCount: 1,
|
||||
expectedVersion: Number(immutablePlan?.version),
|
||||
operationId: `${context.marker}-assign-after-completed`.slice(0, 128),
|
||||
})
|
||||
} })
|
||||
context.addStep(MODULE, 'COMPLETED 后新增计划明细被拒绝', assignmentAfterCompleted,
|
||||
assignmentAfterCompleted.http === 200 && codeOf(assignmentAfterCompleted) === 'A0409',
|
||||
`HTTP=${assignmentAfterCompleted.http}, code=${codeOf(assignmentAfterCompleted)}`, OPTIONS)
|
||||
|
||||
Reference in New Issue
Block a user