test: expand inbound hold acceptance
This commit is contained in:
@@ -11,18 +11,39 @@ test('入库扣货真实 HTTP 模块进入默认全量并依赖订单造数', ()
|
||||
})
|
||||
|
||||
test('入库扣货模块覆盖隔离、幂等、冲突、候选门禁和统一订单投影', () => {
|
||||
assert.match(source, /context\.createdResources\.orders/)
|
||||
assert.match(source, /fixture\.orderNo/)
|
||||
assert.match(source, /初始入库单允许扣货/)
|
||||
assert.match(source, /错误客户不能读取扣货快照/)
|
||||
assert.match(source, /扣货请求同键同载荷重放/)
|
||||
assert.match(source, /扣货同键异载荷返回A0409/)
|
||||
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*:/)
|
||||
})
|
||||
|
||||
test('入库扣货冲突和重放均回查版本及不可变历史零漂移', () => {
|
||||
assert.match(source, /historyFacts\(replayed\) === heldHistoryFacts/)
|
||||
assert.match(source, /扣货冲突后版本、原因和历史零漂移/)
|
||||
assert.match(source, /解除后既有扣货历史身份和内容保持不可变/)
|
||||
assert.match(source, /historyFactsFrom\(historyRows\(released\)\.slice\(1\)\) === heldHistoryFacts/)
|
||||
assert.match(source, /historyFacts\(releaseReplay\) === releasedHistoryFacts/)
|
||||
assert.match(source, /过期版本冲突后解除快照和不可变历史零漂移/)
|
||||
})
|
||||
|
||||
test('入库扣货写操作统一使用请求头 Idempotency-Key 和 expectedVersion', () => {
|
||||
assert.match(source, /context\.request\('POST', path, body, context\.token, \{ 'Idempotency-Key': key \}\)/)
|
||||
assert.match(source, /expectedVersion: initialVersion/)
|
||||
assert.match(source, /expectedVersion: heldVersion/)
|
||||
assert.match(source, /inbound-hold-stale/)
|
||||
})
|
||||
|
||||
test('入库扣货模块只消费本轮 marker 造数且不读取历史资源或环境凭据', () => {
|
||||
assert.match(source, /context\.marker/)
|
||||
assert.match(source, /inboundCode=\$\{encodeURIComponent\(inboundCode\)\}/)
|
||||
assert.doesNotMatch(source, /process\.env|ADMIN_TEST_|MERCHANT_TEST_|history fixture|historical/i)
|
||||
})
|
||||
|
||||
@@ -6,6 +6,15 @@ function id(value) { return value == null ? '' : String(value) }
|
||||
function code(response) { return id(response?.body?.code) || 'missing' }
|
||||
function succeeded(response) { return response?.http === 200 && code(response) === '200' }
|
||||
function rows(page) { return Array.isArray(page?.records) ? page.records : [] }
|
||||
function historyRows(detail) { return Array.isArray(detail?.history) ? detail.history : [] }
|
||||
function historyFactsFrom(items) {
|
||||
return JSON.stringify(items.map((item) => ({
|
||||
id: id(item?.id), action: item?.action || null, reason: item?.reason || null,
|
||||
evidenceFileId: id(item?.evidenceFileId) || null,
|
||||
operatorName: item?.operatorName || null, operatedAt: item?.operatedAt || null,
|
||||
})))
|
||||
}
|
||||
function historyFacts(detail) { return historyFactsFrom(historyRows(detail)) }
|
||||
function channelGroup(channelCode) {
|
||||
const value = String(channelCode || '').toUpperCase()
|
||||
if (value.startsWith('EXX_')) return 'EXX'
|
||||
@@ -34,13 +43,16 @@ export async function run(context) {
|
||||
const fixture = context.createdResources.orders || {}
|
||||
const tenantId = id(fixture.tenantId)
|
||||
const orderId = id(fixture.orderId)
|
||||
const orderNo = id(fixture.orderNo)
|
||||
const inboundId = id(fixture.inboundId)
|
||||
const inboundCode = id(fixture.inboundCode)
|
||||
context.assert(MODULE, '复用本轮订单入库单', null,
|
||||
Boolean(tenantId && orderId && inboundId && inboundCode),
|
||||
`tenantId=${tenantId || 'missing'}, orderId=${orderId || 'missing'}, inboundId=${inboundId || 'missing'}, inboundCode=${inboundCode || 'missing'}`,
|
||||
Boolean(tenantId && orderId && orderNo && inboundId && inboundCode),
|
||||
`tenantId=${tenantId || 'missing'}, orderId=${orderId || 'missing'}, orderNo=${orderNo || 'missing'}, inboundId=${inboundId || 'missing'}, inboundCode=${inboundCode || 'missing'}`,
|
||||
OPTIONS)
|
||||
if (!tenantId || !orderId || !inboundId || !inboundCode) throw new Error('orders fixture missing inbound hold identity')
|
||||
if (!tenantId || !orderId || !orderNo || !inboundId || !inboundCode) {
|
||||
throw new Error('orders fixture missing inbound hold identity')
|
||||
}
|
||||
|
||||
const unifiedPath = `/api/platform/v1/orders/${encodeURIComponent(orderId)}?tenantId=${encodeURIComponent(tenantId)}`
|
||||
const initialUnifiedResponse = await context.request('GET', unifiedPath)
|
||||
@@ -61,7 +73,7 @@ export async function run(context) {
|
||||
evidence(initialHoldResponse), OPTIONS)
|
||||
context.assert(MODULE, '初始入库单允许扣货', initialHoldResponse,
|
||||
initialHold?.holdFlag === false && initialHold?.canHold === true && initialHold?.canRelease === false
|
||||
&& Array.isArray(initialHold?.history),
|
||||
&& historyRows(initialHold).length === 0,
|
||||
`holdFlag=${initialHold?.holdFlag}, canHold=${initialHold?.canHold}, canRelease=${initialHold?.canRelease}, history=${initialHold?.history?.length ?? 'missing'}`,
|
||||
OPTIONS)
|
||||
const initialVersion = Number(initialHold?.version)
|
||||
@@ -96,18 +108,28 @@ export async function run(context) {
|
||||
OPTIONS)
|
||||
const heldVersion = Number(held?.version)
|
||||
const heldHistorySize = held?.history?.length
|
||||
const heldHistoryFacts = historyFacts(held)
|
||||
|
||||
const replayResponse = await write(context, holdPath, holdBody, holdKey)
|
||||
const replayed = context.apiSuccess(MODULE, '扣货请求同键同载荷重放', replayResponse,
|
||||
evidence(replayResponse), OPTIONS)
|
||||
context.assert(MODULE, '扣货幂等重放不增加版本或历史', replayResponse,
|
||||
Number(replayed?.version) === heldVersion && replayed?.history?.length === heldHistorySize,
|
||||
Number(replayed?.version) === heldVersion && replayed?.history?.length === heldHistorySize
|
||||
&& historyFacts(replayed) === heldHistoryFacts,
|
||||
`version=${replayed?.version}, history=${replayed?.history?.length ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const conflictResponse = await write(context, holdPath,
|
||||
{ ...holdBody, reason: `${reason}-冲突` }, holdKey)
|
||||
expectCode(context, '扣货同键异载荷返回A0409', conflictResponse, 'A0409')
|
||||
|
||||
const heldAfterConflictResponse = await context.request('GET', holdPath)
|
||||
const heldAfterConflict = context.apiSuccess(MODULE, '回查扣货冲突后的当前快照', heldAfterConflictResponse,
|
||||
evidence(heldAfterConflictResponse), OPTIONS)
|
||||
context.assert(MODULE, '扣货冲突后版本、原因和历史零漂移', heldAfterConflictResponse,
|
||||
heldAfterConflict?.holdFlag === true && heldAfterConflict?.currentHoldReason === reason
|
||||
&& Number(heldAfterConflict?.version) === heldVersion && historyFacts(heldAfterConflict) === heldHistoryFacts,
|
||||
`version=${heldAfterConflict?.version}, history=${heldAfterConflict?.history?.length ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const heldUnifiedResponse = await context.request('GET', unifiedPath)
|
||||
const heldUnified = context.apiSuccess(MODULE, '扣货后读取统一订单', heldUnifiedResponse,
|
||||
evidence(heldUnifiedResponse), OPTIONS)
|
||||
@@ -117,6 +139,17 @@ export async function run(context) {
|
||||
`holdFlag=${heldUnified?.summary?.holdFlag}, mainStatus=${heldUnified?.summary?.mainStatus}`,
|
||||
OPTIONS)
|
||||
|
||||
const heldUnifiedListResponse = await context.request('GET',
|
||||
`/api/platform/v1/orders?tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}&page=1&size=20`)
|
||||
const heldUnifiedPage = context.apiSuccess(MODULE, '扣货后读取统一订单列表投影', heldUnifiedListResponse,
|
||||
evidence(heldUnifiedListResponse), OPTIONS)
|
||||
const heldUnifiedRow = rows(heldUnifiedPage).find((row) => id(row?.orderId) === orderId)
|
||||
context.assert(MODULE, '统一订单列表同步展示扣货字段', heldUnifiedListResponse,
|
||||
heldUnifiedRow?.holdFlag === true && heldUnifiedRow?.currentHoldReason === reason
|
||||
&& heldUnifiedRow?.mainStatus === mainStatus,
|
||||
`orderId=${orderId}, holdFlag=${heldUnifiedRow?.holdFlag}, mainStatus=${heldUnifiedRow?.mainStatus || 'missing'}`,
|
||||
OPTIONS)
|
||||
|
||||
const heldCandidatesResponse = await context.request('GET', candidatePath)
|
||||
const heldCandidates = context.apiSuccess(MODULE, '扣货后查询智能装柜候选', heldCandidatesResponse,
|
||||
evidence(heldCandidatesResponse), OPTIONS)
|
||||
@@ -141,12 +174,18 @@ export async function run(context) {
|
||||
OPTIONS)
|
||||
const releasedVersion = Number(released?.version)
|
||||
const releasedHistorySize = released?.history?.length
|
||||
const releasedHistoryFacts = historyFacts(released)
|
||||
context.assert(MODULE, '解除后既有扣货历史身份和内容保持不可变', releaseResponse,
|
||||
releasedHistorySize === heldHistorySize + 1
|
||||
&& historyFactsFrom(historyRows(released).slice(1)) === heldHistoryFacts,
|
||||
`before=${heldHistorySize}, after=${releasedHistorySize}`, OPTIONS)
|
||||
|
||||
const releaseReplayResponse = await write(context, releasePath, releaseBody, releaseKey)
|
||||
const releaseReplay = context.apiSuccess(MODULE, '解除请求同键同载荷重放', releaseReplayResponse,
|
||||
evidence(releaseReplayResponse), OPTIONS)
|
||||
context.assert(MODULE, '解除幂等重放不增加版本或历史', releaseReplayResponse,
|
||||
Number(releaseReplay?.version) === releasedVersion && releaseReplay?.history?.length === releasedHistorySize,
|
||||
Number(releaseReplay?.version) === releasedVersion && releaseReplay?.history?.length === releasedHistorySize
|
||||
&& historyFacts(releaseReplay) === releasedHistoryFacts,
|
||||
`version=${releaseReplay?.version}, history=${releaseReplay?.history?.length ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const staleResponse = await write(context, holdPath,
|
||||
@@ -154,6 +193,14 @@ export async function run(context) {
|
||||
`${context.marker}-inbound-hold-stale`.slice(0, 128))
|
||||
expectCode(context, '过期版本再次扣货返回A0409', staleResponse, 'A0409')
|
||||
|
||||
const finalHoldResponse = await context.request('GET', holdPath)
|
||||
const finalHold = context.apiSuccess(MODULE, '回查过期版本冲突后的扣货快照', finalHoldResponse,
|
||||
evidence(finalHoldResponse), OPTIONS)
|
||||
context.assert(MODULE, '过期版本冲突后解除快照和不可变历史零漂移', finalHoldResponse,
|
||||
finalHold?.holdFlag === false && finalHold?.currentHoldReason == null
|
||||
&& Number(finalHold?.version) === releasedVersion && historyFacts(finalHold) === releasedHistoryFacts,
|
||||
`version=${finalHold?.version}, history=${finalHold?.history?.length ?? 'missing'}`, OPTIONS)
|
||||
|
||||
const finalUnifiedResponse = await context.request('GET', unifiedPath)
|
||||
const finalUnified = context.apiSuccess(MODULE, '解除后读取统一订单', finalUnifiedResponse,
|
||||
evidence(finalUnifiedResponse), OPTIONS)
|
||||
@@ -163,6 +210,17 @@ export async function run(context) {
|
||||
`holdFlag=${finalUnified?.summary?.holdFlag}, mainStatus=${finalUnified?.summary?.mainStatus}`,
|
||||
OPTIONS)
|
||||
|
||||
const finalUnifiedListResponse = await context.request('GET',
|
||||
`/api/platform/v1/orders?tenantId=${encodeURIComponent(tenantId)}&orderNo=${encodeURIComponent(orderNo)}&page=1&size=20`)
|
||||
const finalUnifiedPage = context.apiSuccess(MODULE, '解除后读取统一订单列表投影', finalUnifiedListResponse,
|
||||
evidence(finalUnifiedListResponse), OPTIONS)
|
||||
const finalUnifiedRow = rows(finalUnifiedPage).find((row) => id(row?.orderId) === orderId)
|
||||
context.assert(MODULE, '统一订单列表同步恢复扣货字段', finalUnifiedListResponse,
|
||||
finalUnifiedRow?.holdFlag === false && finalUnifiedRow?.currentHoldReason == null
|
||||
&& finalUnifiedRow?.mainStatus === mainStatus,
|
||||
`orderId=${orderId}, holdFlag=${finalUnifiedRow?.holdFlag}, mainStatus=${finalUnifiedRow?.mainStatus || 'missing'}`,
|
||||
OPTIONS)
|
||||
|
||||
const finalCandidatesResponse = await context.request('GET', candidatePath)
|
||||
const finalCandidates = context.apiSuccess(MODULE, '解除后查询智能装柜候选', finalCandidatesResponse,
|
||||
evidence(finalCandidatesResponse), OPTIONS)
|
||||
|
||||
Reference in New Issue
Block a user