254 lines
24 KiB
JavaScript
254 lines
24 KiB
JavaScript
import { deflateRawSync } from 'node:zlib'
|
|
|
|
function id(value) { return value == null ? '' : String(value) }
|
|
function pageOk(value) { return Array.isArray(value?.records) && Number.isFinite(Number(value?.total)) }
|
|
function xlsxOk(response) {
|
|
return response.http === 200 && response.bytes?.length > 4
|
|
&& 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 } }
|
|
}
|
|
|
|
function crc32(bytes) {
|
|
let crc = 0xffffffff
|
|
for (const byte of bytes) { crc ^= byte; for (let bit = 0; bit < 8; bit += 1) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0) }
|
|
return (crc ^ 0xffffffff) >>> 0
|
|
}
|
|
function u16(value) { return Uint8Array.from([value & 255, (value >>> 8) & 255]) }
|
|
function u32(value) { return Uint8Array.from([value & 255, (value >>> 8) & 255, (value >>> 16) & 255, (value >>> 24) & 255]) }
|
|
function concat(...parts) { const result = new Uint8Array(parts.reduce((sum, item) => sum + item.length, 0)); let offset = 0; for (const part of parts) { result.set(part, offset); offset += part.length } return result }
|
|
function xmlEscape(value) { return String(value ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''') }
|
|
function xlsxBytes(headers, values, sheetName, templateVersion = '1') {
|
|
const rows = [['模板版本', templateVersion], ['说明', 'Admin HTTP acceptance fixture'], headers, values]
|
|
const cells = rows.map((row, rowIndex) => `<row r="${rowIndex + 1}">${row.map((value, columnIndex) => `<c r="${String.fromCharCode(65 + columnIndex)}${rowIndex + 1}" t="inlineStr"><is><t>${xmlEscape(value)}</t></is></c>`).join('')}</row>`).join('')
|
|
const files = {
|
|
'[Content_Types].xml': '<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>',
|
|
'_rels/.rels': '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>',
|
|
'xl/workbook.xml': `<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="${xmlEscape(sheetName)}" sheetId="1" r:id="rId1"/></sheets></workbook>`,
|
|
'xl/_rels/workbook.xml.rels': '<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>',
|
|
'xl/worksheets/sheet1.xml': `<?xml version="1.0" encoding="UTF-8"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${cells}</sheetData></worksheet>`,
|
|
}
|
|
const local = []; const central = []; let offset = 0
|
|
for (const [name, text] of Object.entries(files)) { const nameBytes = new TextEncoder().encode(name); const raw = new TextEncoder().encode(text); const compressed = deflateRawSync(raw); const header = concat(Uint8Array.from([0x50,0x4b,0x03,0x04]), u16(20), u16(0), u16(8), u16(0), u16(0), u32(crc32(raw)), u32(compressed.length), u32(raw.length), u16(nameBytes.length), u16(0), nameBytes, compressed); local.push(header); const directory = concat(Uint8Array.from([0x50,0x4b,0x01,0x02]), u16(20), u16(20), u16(0), u16(8), u16(0), u16(0), u32(crc32(raw)), u32(compressed.length), u32(raw.length), u16(nameBytes.length), u16(0), u16(0), u16(0), u16(0), u32(0), u32(offset), nameBytes); central.push(directory); offset += header.length }
|
|
const centralBytes = concat(...central); const localBytes = concat(...local); const end = concat(Uint8Array.from([0x50,0x4b,0x05,0x06]), u16(0), u16(0), u16(central.length), u16(central.length), u32(centralBytes.length), u32(localBytes.length), u16(0)); return concat(localBytes, centralBytes, end)
|
|
}
|
|
|
|
export async function run(context) {
|
|
const module = 'master-data'
|
|
const suffix = context.marker.replace(/\D/g, '').slice(-12)
|
|
|
|
const warehouseListResponse = await context.request('GET', '/api/platform/v1/warehouse-admin?page=1&size=20')
|
|
const warehousePage = context.apiSuccess(module, '查询平台仓库列表', warehouseListResponse)
|
|
context.assert(module, '仓库列表分页结构正确', warehouseListResponse, pageOk(warehousePage), `total=${warehousePage?.total ?? 'n/a'}`)
|
|
const warehousePayload = {
|
|
tenantId: 0, warehouseCode: `AW${suffix}`, warehouseName: `Admin验收仓-${suffix}`,
|
|
warehouseType: 'DOMESTIC', countryCode: 'CN', timezone: 'Asia/Shanghai',
|
|
contactName: '仓库验收员', contactPhone: '13800000001', contactEmail: `warehouse-${suffix}@example.com`,
|
|
status: 'ACTIVE', remark: context.marker,
|
|
address: {
|
|
addressType: 'WAREHOUSE', countryCode: 'CN', stateProvince: '广东省', city: '深圳市',
|
|
district: '南山区', postalCode: '518000', addressLine1: 'Admin API 验收仓地址',
|
|
addressLine2: null, latitude: null, longitude: null,
|
|
},
|
|
capabilities: [{ capabilityCode: 'RECEIVING', capabilityName: '收货', enabled: true, cutoffTime: '16:00:00', description: context.marker }],
|
|
}
|
|
const createWarehouseResponse = await context.request('POST', '/api/platform/v1/warehouse-admin', warehousePayload)
|
|
const warehouseId = id(context.apiSuccess(module, '创建平台公共仓库', createWarehouseResponse))
|
|
context.assert(module, '仓库创建返回字符串兼容 ID', createWarehouseResponse, Boolean(warehouseId), `warehouseId=${warehouseId || 'missing'}`)
|
|
if (!warehouseId) throw new Error('平台仓库创建失败,库位和托盘场景无法继续')
|
|
context.context.warehouseId = warehouseId
|
|
context.createdResources.warehouse = { id: warehouseId, code: warehousePayload.warehouseCode, finalStatus: 'INACTIVE' }
|
|
const warehousePath = `/api/platform/v1/warehouse-admin/${warehouseId}?tenantId=0`
|
|
const warehouseDetailResponse = await context.request('GET', warehousePath)
|
|
const warehouseDetail = context.apiSuccess(module, '查询平台仓库详情', warehouseDetailResponse)
|
|
context.assert(module, '仓库详情、地址和能力正确', warehouseDetailResponse,
|
|
id(warehouseDetail?.warehouse?.id) === warehouseId && warehouseDetail?.addresses?.length === 1 && warehouseDetail?.capabilities?.length === 1,
|
|
`addresses=${warehouseDetail?.addresses?.length ?? 'n/a'}, capabilities=${warehouseDetail?.capabilities?.length ?? 'n/a'}`)
|
|
const editedWarehouse = { ...warehousePayload, warehouseName: `${warehousePayload.warehouseName}-已编辑`, remark: `${context.marker}-EDITED` }
|
|
const updateWarehouseResponse = await context.request('PUT', warehousePath, editedWarehouse)
|
|
context.apiSuccess(module, '编辑平台仓库', updateWarehouseResponse)
|
|
const warehouseAfterEditResponse = await context.request('GET', warehousePath)
|
|
const warehouseAfterEdit = context.apiSuccess(module, '回查编辑后的平台仓库', warehouseAfterEditResponse)
|
|
context.assert(module, '仓库编辑已持久化', warehouseAfterEditResponse,
|
|
warehouseAfterEdit?.warehouse?.warehouseName === editedWarehouse.warehouseName && warehouseAfterEdit?.warehouse?.remark === editedWarehouse.remark,
|
|
`warehouseId=${warehouseId}`)
|
|
|
|
const locationPayload = {
|
|
warehouseId, zoneCode: `Z${suffix.slice(-5)}`, zoneName: 'Admin验收区',
|
|
locationCode: `L${suffix}`, locationName: 'Admin验收库位', locationType: 'SHELF',
|
|
maxWeightKg: 1000, status: 'AVAILABLE', remark: context.marker,
|
|
}
|
|
const createLocationResponse = await context.request('POST', '/api/platform/v1/locations?tenantId=0', locationPayload)
|
|
const locationId = id(context.apiSuccess(module, '创建仓库库位', createLocationResponse))
|
|
if (!locationId) throw new Error('库位创建失败')
|
|
context.createdResources.location = { id: locationId, warehouseId, deleted: false }
|
|
const locationPath = `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}`
|
|
const locationDetailResponse = await context.request('GET', locationPath)
|
|
const locationDetail = context.apiSuccess(module, '查询库位详情', locationDetailResponse)
|
|
context.assert(module, '库位详情关联正确仓库', locationDetailResponse,
|
|
id(locationDetail?.location?.id) === locationId && id(locationDetail?.location?.warehouseId) === warehouseId,
|
|
`locationId=${locationId}`)
|
|
const locationVersion = Number(locationDetail?.location?.version)
|
|
const updateLocationResponse = await context.request('PUT', locationPath, { ...locationPayload, locationName: 'Admin验收库位-已编辑', maxWeightKg: 1200, version: locationVersion })
|
|
context.apiSuccess(module, '编辑库位', updateLocationResponse)
|
|
const locationAfterEditResponse = await context.request('GET', locationPath)
|
|
const locationAfterEdit = context.apiSuccess(module, '回查编辑后的库位', locationAfterEditResponse)
|
|
context.assert(module, '库位编辑与版本递增正确', locationAfterEditResponse,
|
|
locationAfterEdit?.location?.locationName === 'Admin验收库位-已编辑' && Number(locationAfterEdit?.location?.version) === locationVersion + 1,
|
|
`version=${locationAfterEdit?.location?.version ?? 'missing'}`)
|
|
const disabledLocationVersion = Number(locationAfterEdit?.location?.version)
|
|
const disableLocationResponse = await context.request('PATCH', `/api/platform/v1/locations/${locationId}/status?tenantId=0&warehouseId=${warehouseId}`, { status: 'DISABLED', version: disabledLocationVersion })
|
|
context.apiSuccess(module, '停用库位', disableLocationResponse)
|
|
const deleteLocationResponse = await context.request('DELETE', `/api/platform/v1/locations/${locationId}?tenantId=0&warehouseId=${warehouseId}&version=${disabledLocationVersion + 1}`)
|
|
const deletedLocation = context.apiSuccess(module, '删除未占用测试库位', deleteLocationResponse)
|
|
if (deletedLocation === null && String(deleteLocationResponse.body?.code) === '200') context.createdResources.location.deleted = true
|
|
|
|
const palletPayload = { warehouseId, palletType: 'STANDARD', capacityCbm: 1.5, weightKg: 25, color: 'BLUE', remark: context.marker }
|
|
const createPalletResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', palletPayload)
|
|
const palletId = id(context.apiSuccess(module, '创建仓库托盘', createPalletResponse))
|
|
if (!palletId) throw new Error('托盘创建失败')
|
|
context.createdResources.pallet = { id: palletId, warehouseId, deleted: false }
|
|
const palletPath = `/api/platform/v1/pallets/${palletId}?tenantId=0&warehouseId=${warehouseId}`
|
|
const palletDetailResponse = await context.request('GET', palletPath)
|
|
const pallet = context.apiSuccess(module, '查询托盘详情', palletDetailResponse)
|
|
context.assert(module, '托盘详情关联正确仓库', palletDetailResponse,
|
|
id(pallet?.id) === palletId && id(pallet?.warehouseId) === warehouseId, `palletId=${palletId}`)
|
|
const updatePalletResponse = await context.request('PUT', palletPath, { ...palletPayload, capacityCbm: 1.75, weightKg: 26, remark: `${context.marker}-EDITED` })
|
|
context.apiSuccess(module, '编辑托盘', updatePalletResponse)
|
|
const palletAfterEditResponse = await context.request('GET', palletPath)
|
|
const palletAfterEdit = context.apiSuccess(module, '回查编辑后的托盘', palletAfterEditResponse)
|
|
context.assert(module, '托盘编辑已持久化', palletAfterEditResponse,
|
|
Number(palletAfterEdit?.capacityCbm) === 1.75 && palletAfterEdit?.remark === `${context.marker}-EDITED`, `palletId=${palletId}`)
|
|
const deletePalletResponse = await context.request('DELETE', palletPath)
|
|
context.apiSuccess(module, '删除未使用测试托盘', deletePalletResponse)
|
|
if (String(deletePalletResponse.body?.code) === '200') context.createdResources.pallet.deleted = true
|
|
|
|
const expressPayload = {
|
|
companyCode: `EX${suffix}`, companyName: `Admin验收快递-${suffix}`, servicePhone: '4000000000',
|
|
website: 'https://example.com', trackingUrlTemplate: 'https://example.com/{trackingNo}', status: 'ACTIVE', remark: context.marker,
|
|
}
|
|
const createExpressResponse = await context.request('POST', '/api/platform/v1/express-companies?tenantId=0', expressPayload)
|
|
const expressId = id(context.apiSuccess(module, '创建快递公司', createExpressResponse))
|
|
if (!expressId) throw new Error('快递公司创建失败')
|
|
context.createdResources.expressCompany = { id: expressId, code: expressPayload.companyCode, finalStatus: 'INACTIVE' }
|
|
const expressPath = `/api/platform/v1/express-companies/${expressId}?tenantId=0`
|
|
const expressDetailResponse = await context.request('GET', expressPath)
|
|
const express = context.apiSuccess(module, '查询快递公司详情', expressDetailResponse)
|
|
context.assert(module, '快递公司详情正确', expressDetailResponse, id(express?.id) === expressId && express?.companyCode === expressPayload.companyCode, `expressId=${expressId}`)
|
|
const updateExpressResponse = await context.request('PUT', expressPath, { ...expressPayload, companyName: `${expressPayload.companyName}-已编辑`, remark: `${context.marker}-EDITED` })
|
|
context.apiSuccess(module, '编辑快递公司', updateExpressResponse)
|
|
const disableExpressResponse = await context.request('PATCH', `/api/platform/v1/express-companies/${expressId}/status?tenantId=0`, { status: 'INACTIVE' })
|
|
context.apiSuccess(module, '停用快递公司', disableExpressResponse)
|
|
const expressListResponse = await context.request('GET', `/api/platform/v1/express-companies?page=1&size=20&keyword=${expressPayload.companyCode}`)
|
|
const expressPage = context.apiSuccess(module, '筛选快递公司列表', expressListResponse)
|
|
context.assert(module, '快递公司列表回显停用状态', expressListResponse,
|
|
expressPage?.records?.some((item) => id(item.id) === expressId && item.status === 'INACTIVE'), `records=${expressPage?.records?.length ?? 'n/a'}`)
|
|
|
|
const partnerPayload = {
|
|
partnerCode: `PT${suffix}`, partnerName: `Admin验收船公司-${suffix}`, partnerType: 'CARRIER', types: ['CARRIER'],
|
|
carrierCode: 'MATSON',
|
|
contactName: '往来单位验收员', contactPhone: '13800000002', contactEmail: `partner-${suffix}@example.com`,
|
|
countryCode: 'CN', province: '广东省', city: '深圳市', district: '南山区', addressLine: 'Admin API 往来单位地址',
|
|
postalCode: '518000', status: 'ACTIVE', remark: context.marker,
|
|
}
|
|
const createPartnerResponse = await context.request('POST', '/api/platform/v1/partners?tenantId=0', partnerPayload)
|
|
const partnerId = id(context.apiSuccess(module, '创建往来单位', createPartnerResponse))
|
|
if (!partnerId) throw new Error('往来单位创建失败')
|
|
context.createdResources.partner = { id: partnerId, code: partnerPayload.partnerCode, finalStatus: 'INACTIVE' }
|
|
const partnerPath = `/api/platform/v1/partners/${partnerId}?tenantId=0`
|
|
const partnerDetailResponse = await context.request('GET', partnerPath)
|
|
const partner = context.apiSuccess(module, '查询往来单位详情', partnerDetailResponse)
|
|
context.assert(module, '往来单位详情正确', partnerDetailResponse,
|
|
id(partner?.id) === partnerId && partner?.types?.includes('CARRIER'), `partnerId=${partnerId}`)
|
|
const updatePartnerResponse = await context.request('PUT', partnerPath, { ...partnerPayload, partnerName: `${partnerPayload.partnerName}-已编辑`, types: ['CARRIER'], remark: `${context.marker}-EDITED` })
|
|
context.apiSuccess(module, '编辑往来单位', updatePartnerResponse)
|
|
const disablePartnerResponse = await context.request('PATCH', `/api/platform/v1/partners/${partnerId}/status?tenantId=0`, { status: 'INACTIVE' })
|
|
context.apiSuccess(module, '停用往来单位', disablePartnerResponse)
|
|
const partnerListResponse = await context.request('GET', `/api/platform/v1/partners?page=1&size=20&keyword=${partnerPayload.partnerCode}`)
|
|
const partnerPage = context.apiSuccess(module, '筛选往来单位列表', partnerListResponse)
|
|
context.assert(module, '往来单位列表回显编辑与停用状态', partnerListResponse,
|
|
partnerPage?.records?.some((item) => id(item.id) === partnerId && item.status === 'INACTIVE' && item.types?.includes('CARRIER')),
|
|
`records=${partnerPage?.records?.length ?? 'n/a'}`)
|
|
|
|
const templates = [
|
|
['仓库', '/api/platform/v1/warehouse-admin/import-template'],
|
|
['快递公司', '/api/platform/v1/express-companies/import-template'],
|
|
['往来单位', '/api/platform/v1/partners/import-template'],
|
|
]
|
|
const importFixtures = {
|
|
仓库: { headers: ['仓库编码*', '内部编号', '仓库名称', '仓库类型', '国家二字码', '省/州', '城市*', '区/县', '详细地址*', '邮政编码', '时区', '状态', '联系人', '联系电话', '联系邮箱', '备注'], values: [`IMW${suffix}`, '', `导入验收仓-${suffix}`, 'DOMESTIC', 'CN', '广东省', '深圳市', '南山区', `导入地址-${context.marker}`, '518000', 'Asia/Shanghai', 'ACTIVE', '导入联系人', '13800000011', `im-${suffix}@example.com`, context.marker], sheet: '仓库', templateVersion: '2' },
|
|
快递公司: { headers: ['快递公司编码*', '快递公司名称*', '客服电话', '官方网站', '轨迹查询地址模板', '状态*', '备注'], values: [`IME${suffix}`, `导入快递-${suffix}`, '4000000011', 'https://example.com', 'https://example.com/{trackingNo}', 'ACTIVE', context.marker], sheet: '快递公司' },
|
|
往来单位: { headers: ['单位编码*', '单位名称*', '单位类型*', '联系人*', '联系电话*', '联系邮箱', '船司标准编码', '国家二字码*', '省/州', '城市*', '区/县', '详细地址*', '邮政编码', '状态*', '备注'], values: [`IMP${suffix}`, `导入船公司-${suffix}`, 'CARRIER', '导入联系人', '13800000012', `ip-${suffix}@example.com`, 'MATSON', 'CN', '广东省', '深圳市', '南山区', `导入地址-${context.marker}`, '518000', 'ACTIVE', context.marker], sheet: '往来单位' },
|
|
}
|
|
for (const [name, path] of templates) {
|
|
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 fixture = importFixtures[name]
|
|
const fixtureBytes = xlsxBytes(fixture.headers, fixture.values, fixture.sheet, fixture.templateVersion)
|
|
const importPath = `${path.replace('/import-template', '/import')}?tenantId=0`
|
|
const first = await uploadTemplate(context, importPath, fixtureBytes, `${context.marker}-${name}.xlsx`)
|
|
const firstData = first.body?.data
|
|
const importedCount = (result) => Number(result?.importedRows ?? result?.createdRows ?? 0)
|
|
context.addStep(module, `${name}合法 XLSX 带数据行导入`, first,
|
|
first.http === 200 && String(first.body?.code) === '200' && importedCount(firstData) >= 1,
|
|
`HTTP=${first.http}, code=${first.body?.code ?? 'missing'}, importedRows=${importedCount(firstData)}`,
|
|
{ scenario: 'valid-import', deploymentBlocker: first.http === 404 || first.http === 405 || first.http === 503 })
|
|
const second = await uploadTemplate(context, importPath, fixtureBytes, `${context.marker}-${name}-replay.xlsx`)
|
|
const secondData = second.body?.data
|
|
const replayCode = String(second.body?.code ?? '')
|
|
const replayMessage = String(second.body?.message ?? '')
|
|
const idempotentSuccess = replayCode === '200'
|
|
&& (importedCount(secondData) < 1 || (Array.isArray(secondData?.errors) && secondData.errors.length > 0))
|
|
const explicitValidationConflict = replayCode === 'A0422'
|
|
&& replayMessage === `${name}导入文件校验失败`
|
|
context.addStep(module, `${name}重复导入返回明确冲突`, second,
|
|
second.http === 200 && (idempotentSuccess || explicitValidationConflict),
|
|
`HTTP=${second.http}, code=${replayCode || 'missing'}, message=${replayMessage || 'missing'}, firstImported=${importedCount(firstData)}, replayImported=${importedCount(secondData)}, errors=${secondData?.errors?.length ?? 'n/a'}`,
|
|
{ scenario: 'valid-import', deploymentBlocker: second.http === 404 || second.http === 405 || second.http === 503 })
|
|
const key = name === '仓库' ? 'warehouseCode' : name === '快递公司' ? 'companyCode' : 'partnerCode'
|
|
const value = fixture.values[0]
|
|
const listPath = name === '仓库' ? `/api/platform/v1/warehouse-admin?page=1&size=20&keyword=${encodeURIComponent(value)}` : name === '快递公司' ? `/api/platform/v1/express-companies?page=1&size=20&keyword=${encodeURIComponent(value)}` : `/api/platform/v1/partners?page=1&size=20&keyword=${encodeURIComponent(value)}`
|
|
const listed = await context.request('GET', listPath)
|
|
const listedData = context.apiSuccess(module, `${name}导入列表回查`, listed, `keyword=${value}`)
|
|
const matchingItems = listedData?.records?.filter((row) => row[key] === value) ?? []
|
|
const item = matchingItems[0]
|
|
context.assert(module, `${name}重复导入后零新增`, listed, matchingItems.length === 1,
|
|
`matchingRecords=${matchingItems.length}`, { scenario: 'valid-import', deploymentBlocker: listed.http === 404 || listed.http === 405 || listed.http === 503 })
|
|
context.assert(module, `${name}导入资源回查 marker`, listed, Boolean(item), `found=${Boolean(item)}, id=${item?.id ?? 'missing'}`, { scenario: 'valid-import', deploymentBlocker: listed.http === 404 || listed.http === 405 || listed.http === 503 })
|
|
if (item?.id) {
|
|
const disablePath = name === '仓库' ? `/api/platform/v1/warehouse-admin/${item.id}/status?tenantId=0&status=INACTIVE` : name === '快递公司' ? `/api/platform/v1/express-companies/${item.id}/status?tenantId=0` : `/api/platform/v1/partners/${item.id}/status?tenantId=0`
|
|
const disableBody = name === '仓库' ? undefined : { status: 'INACTIVE' }
|
|
const disabled = await context.request('PATCH', disablePath, disableBody)
|
|
context.apiSuccess(module, `${name}导入资源停用清理`, disabled, `id=${item.id}`)
|
|
}
|
|
} else {
|
|
context.addCoverageGap(module, 'valid-import', `${name}合法 XLSX 导入`, '模板下载契约不可用,无法安全取得真实字段模板;未构造伪 XLSX', 'deployment')
|
|
}
|
|
}
|
|
|
|
context.addCoverageGap(module, 'optimistic-lock', '仓库/托盘/快递/往来单位旧 version 冲突',
|
|
'当前稳定 DTO 明确不声明 version/expectedVersion;库位版本冲突由 supplemental-boundaries 覆盖,四期报价/汇率/车辆由 phase4-overseas-master-data 覆盖',
|
|
'documented-non-versioned-contract', { blocking: false })
|
|
context.addStep(module, '非版本化主数据不伪造旧version请求', null, true,
|
|
'仓库/托盘/快递/往来单位沿用当前非版本化稳定契约;版本化资源在各自专项验收',
|
|
{ scenario: 'optimistic-lock', deploymentBlocker: false })
|
|
|
|
const disableWarehouseResponse = await context.request('PATCH', `/api/platform/v1/warehouse-admin/${warehouseId}/status?tenantId=0&status=INACTIVE`)
|
|
context.apiSuccess(module, '停用测试仓库', disableWarehouseResponse)
|
|
}
|