This commit is contained in:
2026-08-08 15:25:46 +08:00
parent 7b2b208467
commit 931009a518
11 changed files with 10920 additions and 3 deletions
@@ -5,6 +5,21 @@ function xlsxOk(response) {
&& response.bytes[0] === 0x50 && response.bytes[1] === 0x4b
}
async function uploadTemplate(context, path, bytes, fileName) {
const form = new FormData()
form.append('file', new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), fileName)
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` },
body: form, signal: AbortSignal.timeout(60_000),
})
const raw = await response.text(); let body = null
try { body = raw ? JSON.parse(raw) : null } catch { /* bounded evidence only */ }
return { method: 'POST', path, http: response.status, body, raw: body === null ? raw.slice(0, 500) : null,
requestId: body?.requestId || response.headers.get('x-request-id'), traceId: body?.traceId || response.headers.get('x-trace-id') }
} catch (error) { return { method: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null } }
}
export async function run(context) {
const module = 'master-data'
const suffix = context.marker.replace(/\D/g, '').slice(-12)
@@ -150,8 +165,30 @@ export async function run(context) {
const response = await context.requestBinary('GET', path)
context.addStep(module, `下载${name}导入模板`, response, xlsxOk(response),
`bytes=${response.bytes?.length ?? 0}, contentType=${response.contentType || 'missing'}`)
if (xlsxOk(response)) {
const importPath = `${path.replace('/import-template', '/import')}?tenantId=0&dryRun=true`
const first = await uploadTemplate(context, importPath, response.bytes, `${context.marker}-${name}.xlsx`)
const firstData = first.body?.data
context.addStep(module, `${name}合法模板 dryRun 导入契约`, first,
first.http === 200 && String(first.body?.code) === '200',
`HTTP=${first.http}, code=${first.body?.code ?? 'missing'}, rows=${firstData?.totalRows ?? 'n/a'}, parserSuccess=${firstData?.success ?? 'n/a'}`,
{ scenario: 'valid-import', deploymentBlocker: first.http === 404 || first.http === 405 || first.http === 503 })
const second = await uploadTemplate(context, importPath, response.bytes, `${context.marker}-${name}-replay.xlsx`)
context.addStep(module, `${name}合法模板重复 dryRun 幂等结果稳定`, second,
second.http === 200 && String(second.body?.code) === '200'
&& second.body?.data?.totalRows === firstData?.totalRows && second.body?.data?.importedRows === firstData?.importedRows,
`firstRows=${firstData?.totalRows ?? 'n/a'}, replayRows=${second.body?.data?.totalRows ?? 'n/a'}`,
{ scenario: 'valid-import', deploymentBlocker: second.http === 404 || second.http === 405 || second.http === 503 })
context.addCoverageGap(module, 'valid-import', `${name}带数据行的合法 XLSX 写入`,
'当前验收运行时无 XLSX 写入依赖;仅上传真实服务模板做解析/重复结果稳定性验证,未伪造数据行或写入主数据', 'runtime-dependency')
} else {
context.addCoverageGap(module, 'valid-import', `${name}合法 XLSX 导入`, '模板下载契约不可用,无法安全取得真实字段模板;未构造伪 XLSX', 'deployment')
}
}
context.addCoverageGap(module, 'optimistic-lock', '仓库/托盘/快递/往来单位旧 version 冲突',
'真实 DTO 未声明 version/expectedVersion 字段,不能伪造并发冲突断言;库位版本已由 supplemental-boundaries 覆盖', 'contract-gap')
const disableWarehouseResponse = await context.request('PATCH', `/api/platform/v1/warehouse-admin/${warehouseId}/status?tenantId=0&status=INACTIVE`)
context.apiSuccess(module, '停用测试仓库', disableWarehouseResponse)
}