优化
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
const MODULE = 'supplemental-boundaries'
|
||||
const OPTIONS = { scenario: 'admin-boundary-automation', deploymentBlocker: true }
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) }
|
||||
function failure(response) {
|
||||
return [400, 401, 403, 404, 409].includes(response?.http)
|
||||
|| (response?.http === 200 && codeOf(response) !== '200')
|
||||
}
|
||||
function missingRoute(response) {
|
||||
return response?.http === 404 || response?.http === 405
|
||||
|| /Not Found|No handler|No static resource/i.test(String(response?.raw || response?.body?.message || ''))
|
||||
}
|
||||
|
||||
async function multipart(context, path, bytes, fileName, contentType = 'application/octet-stream') {
|
||||
const form = new FormData()
|
||||
form.append('file', new Blob([bytes], { type: contentType }), 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 content = await response.text()
|
||||
let body = null
|
||||
try { body = content ? JSON.parse(content) : null } catch { /* bounded raw evidence below */ }
|
||||
return {
|
||||
method: 'POST', path, http: response.status, body,
|
||||
raw: body === null ? content.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 }
|
||||
}
|
||||
}
|
||||
|
||||
function warehousePayload(context, suffix) {
|
||||
return {
|
||||
tenantId: 0, warehouseCode: `BW${suffix}`, warehouseName: `边界验收仓-${suffix}`,
|
||||
warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai',
|
||||
contactName: '边界验收员', contactPhone: '13800000003', contactEmail: `boundary-${suffix}@example.com`,
|
||||
status: 'ACTIVE', remark: context.marker,
|
||||
address: { addressType: 'WAREHOUSE', countryCode: 'CN', stateProvince: '广东省', city: '深圳市', district: '南山区', postalCode: '518000', addressLine1: '边界验收地址', addressLine2: null, latitude: null, longitude: null },
|
||||
capabilities: [{ capabilityCode: 'RECEIVING', capabilityName: '收货', enabled: true, cutoffTime: '16:00:00', description: context.marker }],
|
||||
}
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
const start = context.steps.length
|
||||
const suffix = context.marker.replace(/\D/g, '').slice(-12)
|
||||
let warehouseId = ''
|
||||
let locationId = ''
|
||||
let warehousePayloadValue
|
||||
let locationPayload
|
||||
let locationVersion = 0
|
||||
|
||||
// Create only marker-owned warehouse/location resources for the one
|
||||
// executable optimistic-lock scenario. Pallets and master-data records have
|
||||
// no version field in the public DTO, so they are not falsely claimed here.
|
||||
warehousePayloadValue = warehousePayload(context, suffix)
|
||||
const createWarehouse = await context.request('POST', '/api/platform/v1/warehouse-admin', warehousePayloadValue)
|
||||
warehouseId = id(context.apiSuccess(MODULE, '创建边界验收隔离仓库', createWarehouse, '', OPTIONS))
|
||||
if (warehouseId) {
|
||||
locationPayload = {
|
||||
warehouseId, zoneCode: `BZ${suffix.slice(-5)}`, zoneName: '边界区',
|
||||
locationCode: `BL${suffix}`, locationName: '边界库位', locationType: 'SHELF',
|
||||
maxWeightKg: 1000, status: 'AVAILABLE', remark: context.marker,
|
||||
}
|
||||
const createLocation = await context.request('POST', '/api/platform/v1/locations?tenantId=0', locationPayload)
|
||||
locationId = id(context.apiSuccess(MODULE, '创建边界验收隔离库位', createLocation, '', OPTIONS))
|
||||
}
|
||||
if (locationId) {
|
||||
const locationPath = `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}`
|
||||
const detail = await context.request('GET', locationPath)
|
||||
const data = context.apiSuccess(MODULE, '读取隔离库位版本', detail, '', OPTIONS)
|
||||
locationVersion = Number(data?.location?.version)
|
||||
const firstUpdate = await context.request('PUT', locationPath, { ...locationPayload, locationName: `${locationPayload.locationName}-编辑`, version: locationVersion })
|
||||
context.apiSuccess(MODULE, '更新隔离库位以推进版本', firstUpdate, `version=${locationVersion}`, OPTIONS)
|
||||
const stale = await context.request('PUT', locationPath, { ...locationPayload, locationName: `${locationPayload.locationName}-STALE`, version: locationVersion })
|
||||
const conflict = stale.http === 200 && ['A0409', '409'].includes(codeOf(stale))
|
||||
context.addStep(MODULE, '库位旧 version PUT 返回冲突', stale, conflict, `HTTP=${stale.http}, code=${codeOf(stale)}`, OPTIONS)
|
||||
const after = await context.request('GET', locationPath)
|
||||
const afterData = context.apiSuccess(MODULE, '冲突后回查库位', after, '', OPTIONS)
|
||||
context.assert(MODULE, '库位冲突未造成部分更新', after,
|
||||
afterData?.location?.locationName === `${locationPayload.locationName}-编辑`, `name=${afterData?.location?.locationName || 'missing'}`, OPTIONS)
|
||||
locationVersion = Number(afterData?.location?.version)
|
||||
}
|
||||
|
||||
// Public APIs do not expose a safe way to create a real occupied pallet/
|
||||
// location association without an inbound allocation fixture. Keep this as
|
||||
// an explicit automation gap instead of claiming a deletion-protection pass.
|
||||
context.addStep(MODULE, '托盘/库位真实占用删除保护自动化缺口', null, false,
|
||||
'当前公共验收 API 未提供可安全构造并释放占用关联的独立 fixture;未操作历史资源', OPTIONS)
|
||||
|
||||
// Import boundary probes use only in-memory bytes and dry-run where supported.
|
||||
const bogusXlsx = new Uint8Array([0x50, 0x4b, 0x03, 0x04, 0x00, 0x00])
|
||||
const empty = new Uint8Array()
|
||||
const probes = [
|
||||
['仓库空文件', '/api/platform/v1/warehouse-admin/import?tenantId=0&dryRun=true', empty, `${context.marker}.xlsx`],
|
||||
['仓库缺列伪 XLSX', '/api/platform/v1/warehouse-admin/import?tenantId=0&dryRun=true', bogusXlsx, `${context.marker}-missing.xlsx`],
|
||||
['快递公司错误扩展名', '/api/platform/v1/express-companies/import?tenantId=0', new TextEncoder().encode(context.marker), `${context.marker}.txt`],
|
||||
['往来单位非 XLSX', '/api/platform/v1/partners/import?tenantId=0', new TextEncoder().encode(context.marker), `${context.marker}.txt`],
|
||||
]
|
||||
for (const [name, path, bytes, fileName] of probes) {
|
||||
const response = await multipart(context, path, bytes, fileName)
|
||||
context.addStep(MODULE, `${name}安全失败且不模拟导入成功`, response, failure(response),
|
||||
`HTTP=${response.http}, code=${codeOf(response)}, bytes=${bytes.length}`, { scenario: 'import-boundaries', deploymentBlocker: missingRoute(response) })
|
||||
}
|
||||
|
||||
// Invalid customer/price routes and authentication boundaries are read-only.
|
||||
const invalidFee = await context.request('GET', '/api/platform/v1/iam/tenants/999999999999999999/overseas-storage-fees')
|
||||
context.addStep(MODULE, '错误客户海外仓费用 ID 安全失败', invalidFee, failure(invalidFee),
|
||||
`HTTP=${invalidFee.http}, code=${codeOf(invalidFee)}`, OPTIONS)
|
||||
for (const [name, path] of [
|
||||
['库位列表未授权', '/api/platform/v1/locations?page=1&size=1&tenantId=0'],
|
||||
['下载中心非法页码', '/api/platform/v1/files/export-tasks?page=0&size=1'],
|
||||
['船期价格非法状态', '/api/platform/v1/fba-pricing/carrier-rates/price-books?status=INVALID'],
|
||||
]) {
|
||||
const response = await context.request('GET', path, undefined, name.includes('未授权') ? '' : context.token)
|
||||
const passed = name.includes('未授权') ? response.http === 401 : failure(response)
|
||||
context.addStep(MODULE, `${name}边界响应正确`, response, passed, `HTTP=${response.http}, code=${codeOf(response)}`, OPTIONS)
|
||||
}
|
||||
|
||||
if (locationId) {
|
||||
const disable = await context.request('PATCH', `/api/platform/v1/locations/${locationId}/status?tenantId=0&warehouseId=${warehouseId}`, { status: 'DISABLED', version: locationVersion })
|
||||
context.apiSuccess(MODULE, '停用隔离库位以便清理', disable, '', OPTIONS)
|
||||
const remove = await context.request('DELETE', `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}&version=${locationVersion + 1}`)
|
||||
context.apiSuccess(MODULE, '删除隔离库位', remove, '', OPTIONS)
|
||||
}
|
||||
if (warehouseId) {
|
||||
const disableWarehouse = await context.request('PATCH', `/api/platform/v1/warehouse-admin/${warehouseId}/status?tenantId=0&status=INACTIVE`)
|
||||
context.apiSuccess(MODULE, '停用隔离仓库', disableWarehouse, '', OPTIONS)
|
||||
}
|
||||
|
||||
const steps = context.steps.slice(start)
|
||||
context.scenarios.push({ name: 'admin-boundary-automation', module: MODULE, success: steps.every((step) => step.passed), deploymentBlocked: steps.some((step) => step.deploymentBlocker), passed: steps.filter((step) => step.passed).length, failed: steps.filter((step) => !step.passed).length })
|
||||
}
|
||||
Reference in New Issue
Block a user