112 lines
6.9 KiB
JavaScript
112 lines
6.9 KiB
JavaScript
// Internal probes invoked only by the registered dropship matrix and its fresh fixtures.
|
||
// A failed expectation remains a deployment blocker. Continue other probes only when
|
||
// authoritative task/inventory snapshots prove the failed request changed no state.
|
||
const MODULE = 'dropship-pallet-matrix'
|
||
const OPTIONS = { scenario: 'dropship-outbound-edge-cases', deploymentBlocker: true }
|
||
const BASE = '/api/platform/v1/last-leg/outbound-orders'
|
||
|
||
function record(edge, name, response, passed, evidence = '') {
|
||
edge.context.assert(MODULE, `${edge.label}${name}`, response, passed, evidence, OPTIONS)
|
||
}
|
||
|
||
async function request(edge, method, path, body, pda = false) {
|
||
return edge.context.requestOverseas(method, path, body, {}, pda ? edge.token : edge.context.overseasToken)
|
||
}
|
||
|
||
async function candidates(edge) {
|
||
const rows = await edge.call('边界测试回查本轮库存候选', 'GET', edge.candidatePath)
|
||
return rows.filter(row => edge.group.some(x => x.source.inboundCode === row.inboundCode))
|
||
.map(row => ({ position: row.inventoryPositionId, sku: row.skuCode,
|
||
inbound: row.inboundCode, packages: row.availablePackages }))
|
||
.sort((a, b) => `${a.position}:${a.sku}`.localeCompare(`${b.position}:${b.sku}`))
|
||
}
|
||
|
||
async function taskSnapshot(edge) {
|
||
return edge.call('边界测试回查出库冻结事实', 'GET', `${BASE}/${edge.task.outboundNo}`)
|
||
}
|
||
|
||
export async function outboundCreationGuards(edge) {
|
||
const before = await candidates(edge)
|
||
const first = edge.body.items[0]
|
||
const cases = [
|
||
['超量创建', { ...first, packages: first.packages + 100, cartonMarks: [] }],
|
||
['错误SKU创建', { ...first, sku: `WRONG-${edge.context.marker}` }],
|
||
['不存在箱唛创建', { ...first, packages: 1, cartonMarks: [`MISSING-${edge.context.marker}`] }],
|
||
['重复箱唛创建', { ...first, packages: 2, cartonMarks: [first.cartonMarks[0], first.cartonMarks[0]] }],
|
||
['箱数与箱唛不符创建', { ...first, packages: first.packages + 1 }],
|
||
]
|
||
for (const [index, [name, input]] of cases.entries()) {
|
||
const response = await request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`,
|
||
{ ...edge.body, items: [input], idempotencyKey: `${edge.body.idempotencyKey}-invalid-${index}` })
|
||
record(edge, `${name}返回A0409`, response, response.body?.code === 'A0409')
|
||
const after = await candidates(edge)
|
||
edge.check(`${edge.label}${name}后库存候选零漂移`, null, JSON.stringify(before) === JSON.stringify(after))
|
||
}
|
||
// Two real concurrent requests, same fixture and same key; no parallel fixture workers.
|
||
const body = { ...edge.body, idempotencyKey: `${edge.body.idempotencyKey}-race-cancel` }
|
||
const create = () => request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`, body)
|
||
const responses = await Promise.all([create(), create()])
|
||
const tasks = responses.filter(r => r.body?.code === '200').map(r => r.body.data)
|
||
record(edge, '并发同键创建均成功且只生成一个任务', responses[1],
|
||
tasks.length === 2 && tasks[0].taskNo === tasks[1].taskNo && tasks[0].outboundNo === tasks[1].outboundNo)
|
||
edge.check(`${edge.label}并发创建至少一个可回查结果`, null, tasks.length > 0)
|
||
for (const outboundNo of new Set(tasks.map(t => t.outboundNo))) {
|
||
for (let replay = 0; replay < 2; replay++) {
|
||
const cancelled = await request(edge, 'POST', `${BASE}/${outboundNo}/cancel`, { reason: '本轮未拣取消及重放验收' })
|
||
edge.check(`${edge.label}未拣取消及重复取消成功`, cancelled, cancelled.body?.code === '200')
|
||
}
|
||
const detail = await edge.call('回查取消任务状态', 'GET', `${BASE}/${outboundNo}`)
|
||
edge.check(`${edge.label}取消后明细全部取消`, null, detail.outbound?.status === 'CANCELLED'
|
||
&& detail.pallets.every(p => p.status === 'CANCELLED' && Number(p.pickedPackageCount) === 0))
|
||
}
|
||
edge.check(`${edge.label}取消后逐票SKU可用箱数恢复`, null,
|
||
JSON.stringify(before) === JSON.stringify(await candidates(edge)))
|
||
}
|
||
|
||
export async function outboundTaskGuards(edge) {
|
||
const before = await taskSnapshot(edge)
|
||
const first = edge.body.items[0]
|
||
const changedBodies = [
|
||
['收件人', { ...edge.body, recipient: `${edge.body.recipient}-changed` }],
|
||
['备注', { ...edge.body, remark: `${edge.body.remark || ''}-changed` }],
|
||
['明细', { ...edge.body, items: [{ ...first, packages: first.packages + 1 }, ...edge.body.items.slice(1)] }],
|
||
['箱唛', { ...edge.body, items: [{ ...first, cartonMarks: [`CHANGED-${edge.context.marker}`, ...first.cartonMarks.slice(1)] }, ...edge.body.items.slice(1)] }],
|
||
]
|
||
for (const [field, body] of changedBodies) {
|
||
const changed = await request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`, body)
|
||
record(edge, `出库创建同键异载荷返回A0409:${field}`, changed, changed.body?.code === 'A0409')
|
||
edge.check(`${edge.label}同键修改${field}后出库事实零漂移`, null,
|
||
JSON.stringify(before) === JSON.stringify(await taskSnapshot(edge)))
|
||
}
|
||
const path = `/api/pda/v1/last-leg/outbound-tasks/${edge.task.taskNo}`
|
||
const incomplete = await request(edge, 'POST', `${path}/complete`,
|
||
{ idempotencyKey: `${edge.body.idempotencyKey}-incomplete` }, true)
|
||
record(edge, '未拣齐禁止完成出库', incomplete, incomplete.body?.code === 'A0409')
|
||
edge.check(`${edge.label}未拣齐完成失败后零漂移`, null,
|
||
JSON.stringify(before) === JSON.stringify(await taskSnapshot(edge)))
|
||
}
|
||
|
||
export async function probePartialPick(edge) {
|
||
const before = await taskSnapshot(edge)
|
||
const path = `/api/pda/v1/last-leg/outbound-tasks/${edge.task.taskNo}/pick`
|
||
const partial = { ...edge.pick, packages: 1, cartonMarks: edge.pick.cartonMarks.slice(0, 1),
|
||
idempotencyKey: `${edge.pick.idempotencyKey}-partial` }
|
||
const result = await request(edge, 'POST', path, partial, true)
|
||
record(edge, '四箱任务先拣一箱成功', result, result.body?.code === '200')
|
||
const after = await taskSnapshot(edge)
|
||
if (result.body?.code !== '200') {
|
||
edge.check(`${edge.label}分批拣货失败后无库存明细变化`, null, JSON.stringify(before) === JSON.stringify(after))
|
||
// Keep the failed assertion; whole-item picking below only continues later coverage.
|
||
return 0
|
||
}
|
||
edge.check(`${edge.label}分批首箱只累计一箱`, null,
|
||
after.pallets.reduce((sum, p) => sum + Number(p.pickedPackageCount), 0) === 1)
|
||
const replay = await request(edge, 'POST', path, partial, true)
|
||
record(edge, '分批首箱同键重放成功', replay, replay.body?.code === '200')
|
||
edge.check(`${edge.label}分批重放不重复扣减`, null, JSON.stringify(after) === JSON.stringify(await taskSnapshot(edge)))
|
||
const cancel = await request(edge, 'POST', `${BASE}/${edge.task.outboundNo}/cancel`, { reason: '已拣取消拦截验收' })
|
||
record(edge, '部分已拣禁止取消', cancel, cancel.body?.code === 'A0409')
|
||
edge.check(`${edge.label}已拣取消失败后零漂移`, null, JSON.stringify(after) === JSON.stringify(await taskSnapshot(edge)))
|
||
return 1
|
||
}
|