This commit is contained in:
2026-09-15 18:17:06 +08:00
parent 45018987ef
commit 4b671bb0ab
13 changed files with 174 additions and 15 deletions
+2
View File
@@ -4,6 +4,8 @@
总入口是 `../admin-test-env-acceptance.mjs`。每个业务模块使用独立的同名 `.mjs` 文件,避免多个对话同时修改总入口。
退货专项同时核验新建退货单、退货主单、签收单、批次和自动箱唛的完整业务编号:`RT/RR/RB/RC/RETU-` 加 25 位大写 Base36 UUID 编码;不再截取内部 ID 六位尾号。幂等重放仍核对同一已保存编号,手工指定箱唛不受系统生成格式约束。编号生成器另有完整 128 位编码往返、原截断碰撞和本地并行生成单测;本地生成压测不增加 HTTP 并发。
退货专项只运行 `--modules phase4-overseas-return-receipt`,自动准备本轮客户;覆盖非法/重复预报、退货单号和 SKU 查询、非法/未知 SKU 实收、超收及失败不落账、分批签收完成、部分签收禁止合箱、合箱非法箱规/数量/来源、幂等重放前后库存快照一致、逐箱分页无重复遗漏、客户 SKU 数量守恒及标签。通过显式测试环境文件运行总入口,JSON 路径由 `--output` 指定;会创建隔离测试数据,finally 停用临时 PDA,不删除业务记录。失败保留报告,先核对记录再决定是否新批次重跑。
`return-boundary-scenarios.mjs` 是退货专项自动调用的内部扩展,不单独注册入口:本轮新建第二隔离客户,复用两个有效海外仓,覆盖跨客户单据/箱/标签/列表隔离、跨仓 PDA 隔离、两人争抢最后一件的签收及合箱、同键并发签收与成功后重试、多单同 SKU 全量合箱和实际来源追溯、耗尽箱清零、多单 SKU 候选和输入边界。异常写入逐次前后回查订单、签收批次、箱及 SKU;同键重试只模拟响应丢失后的客户端行为,不代表网络故障注入。每组并发严格为两个请求,不启动批量 worker;缺少第二仓或操作员属于失败,不能跳过后宣称通过。finally 停用新增 PDA,保留测试客户和业务记录;不触发部署、SQL、换标、出库或二次合箱。真实运行须明确授权目标环境及此小规模并发边界。`merchant-qualification-fixture.mjs` 的内部参数 `publishEnvironment: false` 只将第二客户凭据留在独立内存上下文,不覆盖主客户环境凭据,默认主 fixture 行为不变。
@@ -112,6 +112,15 @@ test('流水断言逐行核对归属和业务引用,不能仅凭合计相等
assert.equal(exactReceiptLedger(undefined, []), false)
})
test('真实退货验收检查五类完整编号且保留同键重放检查', async () => {
const source = await readFile(new URL('return-boundary-scenarios.mjs', import.meta.url), 'utf8')
for (const prefix of ['RT', 'RETU', 'RR', 'RB', 'RC']) {
assert.ok(source.includes(`/^${prefix}-[0-9A-Z]{25}$/`), prefix)
}
assert.ok(source.includes('同键并发合箱后重试仍返回原箱'))
assert.ok(source.includes('签收重放不重复产生流水'))
})
test('高价值扩展覆盖混合回滚、同键合箱、同码隔离、权限和流水且仍逐次回查', async () => {
const source = await readFile(new URL('return-boundary-scenarios.mjs', import.meta.url), 'utf8')
for (const name of ['混合明细整笔回滚', '同键并发合箱', '相同SKU跨客户跨仓隔离', '身份入口权限', '签收流水与余额核对']) {
@@ -75,6 +75,8 @@ export async function runReturnBoundaries(context, { merchantToken, warehouse, p
items,
})
resources.returnNos.push(data.order.returnNo)
check('新退货单及退货主单采用独立完整编号', null,
/^RT-[0-9A-Z]{25}$/.test(data.order.returnNo) && /^RETU-[0-9A-Z]{25}$/.test(data.order.lastlegOrderNo))
return { returnNo: data.order.returnNo, sku, skus: items.map(item => item.skuCode), quantity, token, warehouseId }
}
function receiptBody(order, quantity = order.quantity, idempotencyKey = key('RECEIPT')) {
@@ -82,6 +84,9 @@ export async function runReturnBoundaries(context, { merchantToken, warehouse, p
}
async function receive(order, operator = picker) {
const data = await good(operator.token, 'POST', '准备边界场景实收库存', `${RECEIPTS}/confirm`, receiptBody(order))
check('签收单批次及自动箱唛采用独立完整编号', null,
/^RR-[0-9A-Z]{25}$/.test(data.receiptNo) && /^RB-[0-9A-Z]{25}$/.test(data.receiptBatchNo)
&& /^RC-[0-9A-Z]{25}$/.test(data.cartonMark))
order.cartonMark = data.cartonMark
order.batch = data.receiptBatchNo
resources.cartonMarks.push(data.cartonMark)
@@ -284,7 +289,7 @@ export async function runReturnBoundaries(context, { merchantToken, warehouse, p
const responses = await Promise.all([1, 2].map(() => request(picker.token, 'POST', MERGE, body)))
for (const [index, response] of responses.entries()) check(`同键并发合箱请求${index + 1}成功`, response, ok(response))
check('同键并发合箱复用同一目标箱', responses[0], responses.every(ok)
&& Boolean(responses[0].body.data?.cartonMark) && responses[0].body.data.cartonMark === responses[1].body.data.cartonMark)
&& /^RC-[0-9A-Z]{25}$/.test(responses[0].body.data?.cartonMark) && responses[0].body.data.cartonMark === responses[1].body.data.cartonMark)
const [state] = await snapshot([order])
check('同键并发合箱只扣一次且原箱新箱各一件', null, Number(state.cartons.total) === 2
&& rows(state.cartons).every(row => Number(row.totalQuantity) === 1 && row.status === 'AVAILABLE')