From 0aecc90d11d10152fba8f3dd2c9d0ccd2e1e3a29 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 12:16:19 +0800 Subject: [PATCH] test: cover concurrent inbound hold --- .../inbound-hold-contract.test.mjs | 2 ++ .../admin-test-env-modules/inbound-hold.mjs | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/scripts/admin-test-env-modules/inbound-hold-contract.test.mjs b/scripts/admin-test-env-modules/inbound-hold-contract.test.mjs index a6a0d51da..5bb451eae 100644 --- a/scripts/admin-test-env-modules/inbound-hold-contract.test.mjs +++ b/scripts/admin-test-env-modules/inbound-hold-contract.test.mjs @@ -32,6 +32,8 @@ test('入库扣货模块覆盖隔离、幂等、冲突、候选门禁和统一 assert.match(source, /上传本轮扣货关联凭证/) assert.match(source, /尚未进入不可变历史的凭证禁止下载/) assert.match(source, /进入不可变历史后的扣货凭证可下载且内容一致/) + assert.match(source, /同版本并发扣货恰好一个成功一个冲突/) + assert.match(source, /并发验收最终恢复未扣货且历史完整/) assert.match(source, /summary\?\.holdFlag/) assert.match(source, /summary\?\.currentHoldReason/) assert.doesNotMatch(source, /\.holdCargo\b|\.holdReason\b|\bholdCargo\s*:|\bholdReason\s*:/) diff --git a/scripts/admin-test-env-modules/inbound-hold.mjs b/scripts/admin-test-env-modules/inbound-hold.mjs index 9e179f953..ae12f1128 100644 --- a/scripts/admin-test-env-modules/inbound-hold.mjs +++ b/scripts/admin-test-env-modules/inbound-hold.mjs @@ -313,4 +313,34 @@ export async function run(context) { rows(finalCandidates).some((row) => id(row?.inboundId) === inboundId), `inboundId=${inboundId}, candidates=${rows(finalCandidates).map((row) => id(row?.inboundId)).join(',') || 'empty'}`, OPTIONS) + + const concurrentReasons = [`${context.marker} 并发扣货A`, `${context.marker} 并发扣货B`] + const concurrentResponses = await Promise.all(concurrentReasons.map((concurrentReason, index) => + write(context, holdPath, { expectedVersion: releasedVersion, reason: concurrentReason }, + `${context.marker}-inbound-hold-race-${index}`.slice(0, 128)))) + const concurrentCodes = concurrentResponses.map(code).sort() + context.assert(MODULE, '同版本并发扣货恰好一个成功一个冲突', null, + JSON.stringify(concurrentCodes) === JSON.stringify(['200', 'A0409']), + `codes=${concurrentCodes.join(',')}`, OPTIONS) + const concurrentSnapshotResponse = await context.request('GET', holdPath) + const concurrentSnapshot = context.apiSuccess(MODULE, '回查并发扣货后的唯一快照', + concurrentSnapshotResponse, evidence(concurrentSnapshotResponse), OPTIONS) + context.assert(MODULE, '并发扣货只递增一次版本并新增一条历史', concurrentSnapshotResponse, + concurrentSnapshot?.holdFlag === true + && concurrentReasons.includes(concurrentSnapshot?.currentHoldReason) + && Number(concurrentSnapshot?.version) === releasedVersion + 1 + && historyRows(concurrentSnapshot).length === releasedHistorySize + 1, + `version=${concurrentSnapshot?.version}, history=${concurrentSnapshot?.history?.length ?? 'missing'}, reason=${concurrentSnapshot?.currentHoldReason || 'missing'}`, + OPTIONS) + + const cleanupResponse = await write(context, releasePath, + { expectedVersion: Number(concurrentSnapshot?.version), reason: `${context.marker} 并发验收恢复` }, + `${context.marker}-inbound-race-cleanup`.slice(0, 128)) + const cleanup = context.apiSuccess(MODULE, '并发验收后解除扣货恢复订单', cleanupResponse, + evidence(cleanupResponse), OPTIONS) + context.assert(MODULE, '并发验收最终恢复未扣货且历史完整', cleanupResponse, + cleanup?.holdFlag === false && cleanup?.currentHoldReason == null + && Number(cleanup?.version) === releasedVersion + 2 + && historyRows(cleanup).length === releasedHistorySize + 2, + `version=${cleanup?.version}, history=${cleanup?.history?.length ?? 'missing'}`, OPTIONS) }