|
|
|
@@ -39,6 +39,13 @@ function write(context, path, body, key) {
|
|
|
|
|
return context.request('POST', path, body, context.token, { 'Idempotency-Key': key })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sameSnapshot(actual, expected) {
|
|
|
|
|
return actual?.holdFlag === expected?.holdFlag
|
|
|
|
|
&& actual?.currentHoldReason === expected?.currentHoldReason
|
|
|
|
|
&& Number(actual?.version) === Number(expected?.version)
|
|
|
|
|
&& historyFacts(actual) === historyFacts(expected)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function run(context) {
|
|
|
|
|
const fixture = context.createdResources.orders || {}
|
|
|
|
|
const tenantId = id(fixture.tenantId)
|
|
|
|
@@ -95,21 +102,87 @@ export async function run(context) {
|
|
|
|
|
`/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold?tenantId=${encodeURIComponent(foreignTenantId)}`)
|
|
|
|
|
expectCode(context, '错误客户不能读取扣货快照', foreignResponse, 'A0404')
|
|
|
|
|
|
|
|
|
|
const invalidCommands = [
|
|
|
|
|
['扣货缺少幂等键返回A0400', context.request('POST', holdPath,
|
|
|
|
|
{ expectedVersion: initialVersion, reason: '缺少幂等键' }, context.token), 'A0400'],
|
|
|
|
|
['扣货空白原因返回A0422', write(context, holdPath,
|
|
|
|
|
{ expectedVersion: initialVersion, reason: ' ' }, `${context.marker}-hold-blank`), 'A0422'],
|
|
|
|
|
['扣货超长原因返回A0422', write(context, holdPath,
|
|
|
|
|
{ expectedVersion: initialVersion, reason: '测'.repeat(1001) }, `${context.marker}-hold-long-reason`), 'A0422'],
|
|
|
|
|
['扣货负版本返回A0422', write(context, holdPath,
|
|
|
|
|
{ expectedVersion: -1, reason: '负版本' }, `${context.marker}-hold-negative-version`), 'A0422'],
|
|
|
|
|
['扣货无效凭证ID返回A0422', write(context, holdPath,
|
|
|
|
|
{ expectedVersion: initialVersion, reason: '无效凭证', evidenceFileId: 0 }, `${context.marker}-hold-bad-evidence`), 'A0422'],
|
|
|
|
|
['错误客户不能执行扣货', write(context,
|
|
|
|
|
`/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold?tenantId=${encodeURIComponent(foreignTenantId)}`,
|
|
|
|
|
{ expectedVersion: initialVersion, reason: '跨客户扣货' }, `${context.marker}-hold-foreign`), 'A0404'],
|
|
|
|
|
]
|
|
|
|
|
for (const [label, pending, expectedCode] of invalidCommands) {
|
|
|
|
|
expectCode(context, label, await pending, expectedCode)
|
|
|
|
|
}
|
|
|
|
|
const afterInvalidResponse = await context.request('GET', holdPath)
|
|
|
|
|
const afterInvalid = context.apiSuccess(MODULE, '回查非法命令后的初始快照', afterInvalidResponse,
|
|
|
|
|
evidence(afterInvalidResponse), OPTIONS)
|
|
|
|
|
context.assert(MODULE, '非法命令不改变版本、状态或历史', afterInvalidResponse,
|
|
|
|
|
sameSnapshot(afterInvalid, initialHold),
|
|
|
|
|
`version=${afterInvalid?.version}, history=${afterInvalid?.history?.length ?? 'missing'}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const evidenceBytes = new TextEncoder().encode(`%PDF-1.4\n${context.marker}\n%%EOF`)
|
|
|
|
|
const evidencePath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold/evidence?tenantId=${encodeURIComponent(tenantId)}`
|
|
|
|
|
const evidenceResponse = await context.requestMultipart('POST', evidencePath, {
|
|
|
|
|
content: evidenceBytes, type: 'application/pdf', name: `${context.marker}-hold.pdf`,
|
|
|
|
|
})
|
|
|
|
|
const evidenceFile = context.apiSuccess(MODULE, '上传本轮扣货关联凭证', evidenceResponse,
|
|
|
|
|
evidence(evidenceResponse), OPTIONS)
|
|
|
|
|
const evidenceFileId = id(evidenceFile?.fileId)
|
|
|
|
|
context.assert(MODULE, '扣货凭证返回安全文件元数据和字符串长整型ID', evidenceResponse,
|
|
|
|
|
Boolean(evidenceFileId) && typeof evidenceFile?.fileId === 'string'
|
|
|
|
|
&& evidenceFile?.fileName === `${context.marker}-hold.pdf`
|
|
|
|
|
&& evidenceFile?.contentType === 'application/pdf'
|
|
|
|
|
&& Number(evidenceFile?.sizeBytes) === evidenceBytes.length,
|
|
|
|
|
`fileId=${evidenceFileId || 'missing'}, fileName=${evidenceFile?.fileName || 'missing'}, contentType=${evidenceFile?.contentType || 'missing'}`,
|
|
|
|
|
OPTIONS)
|
|
|
|
|
if (!evidenceFileId) throw new Error('hold evidence upload missing fileId')
|
|
|
|
|
|
|
|
|
|
const evidenceDownloadPath = `/api/platform/inbounds/${encodeURIComponent(inboundId)}/hold/evidence/${encodeURIComponent(evidenceFileId)}/download?tenantId=${encodeURIComponent(tenantId)}`
|
|
|
|
|
const unreferencedDownload = await context.requestBinary('GET', evidenceDownloadPath)
|
|
|
|
|
expectCode(context, '尚未进入不可变历史的凭证禁止下载', unreferencedDownload, 'A0404')
|
|
|
|
|
|
|
|
|
|
const reason = `${context.marker} 财务复核扣货`.slice(0, 1000)
|
|
|
|
|
const holdKey = `${context.marker}-inbound-hold`.slice(0, 128)
|
|
|
|
|
const holdBody = { expectedVersion: initialVersion, reason }
|
|
|
|
|
const holdBody = { expectedVersion: initialVersion, reason, evidenceFileId }
|
|
|
|
|
const holdResponse = await write(context, holdPath, holdBody, holdKey)
|
|
|
|
|
const held = context.apiSuccess(MODULE, '手工扣货入库单', holdResponse, evidence(holdResponse), OPTIONS)
|
|
|
|
|
context.assert(MODULE, '扣货写入当前快照和不可变历史', holdResponse,
|
|
|
|
|
held?.holdFlag === true && held?.currentHoldReason === reason && held?.canHold === false
|
|
|
|
|
&& held?.canRelease === true && Number(held?.version) === initialVersion + 1
|
|
|
|
|
&& held?.history?.[0]?.action === 'HOLD' && held?.history?.[0]?.reason === reason,
|
|
|
|
|
&& held?.history?.[0]?.action === 'HOLD' && held?.history?.[0]?.reason === reason
|
|
|
|
|
&& id(held?.history?.[0]?.evidenceFileId) === evidenceFileId,
|
|
|
|
|
`holdFlag=${held?.holdFlag}, version=${held?.version}, history=${held?.history?.length ?? 'missing'}`,
|
|
|
|
|
OPTIONS)
|
|
|
|
|
const heldVersion = Number(held?.version)
|
|
|
|
|
const heldHistorySize = held?.history?.length
|
|
|
|
|
const heldHistoryFacts = historyFacts(held)
|
|
|
|
|
|
|
|
|
|
const evidenceDownload = await context.requestBinary('GET', evidenceDownloadPath)
|
|
|
|
|
context.assert(MODULE, '进入不可变历史后的扣货凭证可下载且内容一致', evidenceDownload,
|
|
|
|
|
evidenceDownload?.http === 200 && evidenceDownload?.body == null
|
|
|
|
|
&& evidenceDownload?.contentType?.includes('application/pdf')
|
|
|
|
|
&& Buffer.from(evidenceDownload?.bytes || []).equals(Buffer.from(evidenceBytes)),
|
|
|
|
|
`HTTP=${evidenceDownload?.http ?? 'missing'}, contentType=${evidenceDownload?.contentType || 'missing'}, bytes=${evidenceDownload?.bytes?.length ?? 'missing'}`,
|
|
|
|
|
OPTIONS)
|
|
|
|
|
|
|
|
|
|
const duplicateStateResponse = await write(context, holdPath,
|
|
|
|
|
{ expectedVersion: heldVersion, reason: `${context.marker} 重复状态` },
|
|
|
|
|
`${context.marker}-inbound-hold-again`.slice(0, 128))
|
|
|
|
|
expectCode(context, '已扣货时使用新键再次扣货返回A0409', duplicateStateResponse, 'A0409')
|
|
|
|
|
const heldAfterDuplicateResponse = await context.request('GET', holdPath)
|
|
|
|
|
const heldAfterDuplicate = context.apiSuccess(MODULE, '回查重复扣货后的当前快照', heldAfterDuplicateResponse,
|
|
|
|
|
evidence(heldAfterDuplicateResponse), OPTIONS)
|
|
|
|
|
context.assert(MODULE, '重复扣货不改变版本、原因或历史', heldAfterDuplicateResponse,
|
|
|
|
|
sameSnapshot(heldAfterDuplicate, held),
|
|
|
|
|
`version=${heldAfterDuplicate?.version}, history=${heldAfterDuplicate?.history?.length ?? 'missing'}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const replayResponse = await write(context, holdPath, holdBody, holdKey)
|
|
|
|
|
const replayed = context.apiSuccess(MODULE, '扣货请求同键同载荷重放', replayResponse,
|
|
|
|
|
evidence(replayResponse), OPTIONS)
|
|
|
|
@@ -180,6 +253,18 @@ export async function run(context) {
|
|
|
|
|
&& historyFactsFrom(historyRows(released).slice(1)) === heldHistoryFacts,
|
|
|
|
|
`before=${heldHistorySize}, after=${releasedHistorySize}`, OPTIONS)
|
|
|
|
|
|
|
|
|
|
const duplicateReleaseResponse = await write(context, releasePath,
|
|
|
|
|
{ expectedVersion: releasedVersion, reason: `${context.marker} 重复解除` },
|
|
|
|
|
`${context.marker}-inbound-release-again`.slice(0, 128))
|
|
|
|
|
expectCode(context, '未扣货时使用新键再次解除返回A0409', duplicateReleaseResponse, 'A0409')
|
|
|
|
|
const releasedAfterDuplicateResponse = await context.request('GET', holdPath)
|
|
|
|
|
const releasedAfterDuplicate = context.apiSuccess(MODULE, '回查重复解除后的当前快照',
|
|
|
|
|
releasedAfterDuplicateResponse, evidence(releasedAfterDuplicateResponse), OPTIONS)
|
|
|
|
|
context.assert(MODULE, '重复解除不改变版本、状态或历史', releasedAfterDuplicateResponse,
|
|
|
|
|
sameSnapshot(releasedAfterDuplicate, released),
|
|
|
|
|
`version=${releasedAfterDuplicate?.version}, history=${releasedAfterDuplicate?.history?.length ?? 'missing'}`,
|
|
|
|
|
OPTIONS)
|
|
|
|
|
|
|
|
|
|
const releaseReplayResponse = await write(context, releasePath, releaseBody, releaseKey)
|
|
|
|
|
const releaseReplay = context.apiSuccess(MODULE, '解除请求同键同载荷重放', releaseReplayResponse,
|
|
|
|
|
evidence(releaseReplayResponse), OPTIONS)
|
|
|
|
|