test: strengthen overseas warehouse PDA contracts

This commit is contained in:
2026-08-24 19:41:16 +08:00
parent 1e4b216afa
commit bffe7d3b23
5 changed files with 192 additions and 31 deletions
@@ -12,7 +12,7 @@ const source = fs.readFileSync(path.join(here, 'phase4-overseas-warehouse.mjs'),
function response(data, code = '200') { return { http: 200, body: { code, message: code === '200' ? 'success' : 'conflict', data }, requestId: 'request-test', traceId: 'trace-test' } }
function page(records = []) { return { records, page: 1, size: 20, total: records.length, pages: records.length ? 1 : 0 } }
function contextFixture({ tenants = [{ id: '10001', status: 'ACTIVE' }], warehouseRecords = [{ id: '20001', status: 'ACTIVE', warehouseCode: 'LAX', warehouseName: '洛杉矶仓' }] } = {}) {
const calls = []; const steps = []; const gaps = []; let createdPallet = null
const calls = []; const steps = []; const gaps = []; let createdPallet = null; let createdLocation = null; let createdRelabel = null
const context = {
context: {}, createdResources: {}, marker: 'ACC-PHASE4-TEST', calls, steps, gaps,
async request(method, requestPath) { calls.push({ channel: 'domestic', method, path: requestPath }); return response(page(tenants)) },
@@ -20,11 +20,23 @@ function contextFixture({ tenants = [{ id: '10001', status: 'ACTIVE' }], warehou
calls.push({ channel: 'overseas', method, path: requestPath, body })
if (method === 'POST' && requestPath === '/api/platform/v1/warehouse-admin') return response('20001')
if (method === 'PATCH' && requestPath.includes('/warehouse-admin/20001/status?')) return response(null)
if (method === 'POST' && requestPath.endsWith('/warehouse-admin/20001/locations')) { createdLocation = { id: '72002', customerId: '0', warehouseId: '20001', ...body }; return response('72002') }
if (method === 'PUT' && requestPath.endsWith('/warehouse-admin/20001/locations/72002')) { createdLocation = { ...createdLocation, ...body }; return response(null) }
if (method === 'DELETE' && requestPath.endsWith('/warehouse-admin/20001/locations/72002')) { createdLocation = null; return response(null) }
if (method === 'POST' && requestPath.endsWith('/warehouse-admin/20001/pallets')) { createdPallet = { id: '71001', customerId: '0', warehouseId: '20001', locationId: '72001', version: 0, ...body }; return response('71001') }
if (method === 'PUT' && requestPath.endsWith('/warehouse-admin/20001/pallets/71001')) {
if (Number(body.version) !== Number(createdPallet.version)) return response(null, 'A0409')
createdPallet = { ...createdPallet, ...body, version: createdPallet.version + 1 }; return response(null)
}
if (method === 'POST' && requestPath.startsWith('/api/platform/v1/last-leg/relabel-applications?')) {
if (createdRelabel && body.remark !== createdRelabel.remark) return response(null, 'A0409')
if (!createdRelabel) createdRelabel = { ...body, relabelNo: 'FBR-TEST', status: 'PENDING_PROCESSING' }
return response({ application: createdRelabel, items: body.items, history: [] })
}
if (method === 'PUT' && requestPath.endsWith('/relabel-applications/FBR-TEST/status')) {
createdRelabel = { ...createdRelabel, status: body.status }
return response({ application: createdRelabel, items: createdRelabel.items, history: [] })
}
if (requestPath.startsWith('/api/platform/v1/last-leg/orders?')) return response(page([{ id: '30001', orderNo: 'LL-1', currentStatus: 'PENDING_ASSIGNMENT' }]))
if (requestPath.includes('/last-leg/orders/30001?')) return response({ order: { id: '30001' }, cargo: [], statusHistory: [] })
if (requestPath.startsWith('/api/platform/v1/last-leg/relabel-applications?')) return response(page([{ id: '40001', relabelNo: 'RL-1', status: 'PENDING_PROCESSING' }]))
@@ -37,6 +49,9 @@ function contextFixture({ tenants = [{ id: '10001', status: 'ACTIVE' }], warehou
if (requestPath.includes('/inventory/ledger?')) return response(page([{ id: '62001', customerId: '10001', businessType: 'STOCK_IN', beforePackages: 0, deltaPackages: 10, afterPackages: 10, occurredAt: '2026-08-21T10:00:00' }]))
if (requestPath.startsWith('/api/platform/v1/warehouse-admin?')) return response(page(warehouseRecords))
if (requestPath.includes('/warehouse-admin/20001?')) return response({ warehouse: { id: '20001', customerId: '10001', status: 'ACTIVE' } })
if (requestPath.endsWith('/warehouse-admin/20001/locations?page=1&size=20')) return response(page([{ id: '72001', customerId: '0', warehouseId: '20001', zoneCode: 'A', locationCode: 'A-01', locationType: 'STORAGE', maxLoadKg: 1000, status: 'ACTIVE' }]))
if (requestPath.endsWith('/warehouse-admin/20001/locations/72001')) return response({ id: '72001', customerId: '0', warehouseId: '20001', locationCode: 'A-01', status: 'ACTIVE' })
if (requestPath.endsWith('/warehouse-admin/20001/locations/72002')) return response(createdLocation)
if (requestPath.endsWith('/warehouse-admin/20001/pallets?page=1&size=20')) return response(page([{ id: '70001', customerId: '0', warehouseId: '20001', palletCode: 'PLT-1', status: 'IDLE' }]))
if (requestPath.endsWith('/warehouse-admin/20001/pallets/70001')) return response({ id: '70001', customerId: '0', warehouseId: '20001' })
if (requestPath.endsWith('/warehouse-admin/20001/pallets/71001')) return response(createdPallet)
@@ -59,16 +74,18 @@ test('模块只依赖注入请求,不读取环境或自行联网', () => {
assert.doesNotMatch(source, /process\.env|fetch\(|requestBinary/)
})
test('既有仓库场景只写本轮托盘生命周期', () => {
test('既有仓库场景只写本轮库位托盘与换标生命周期', () => {
const context = contextFixture()
return run(context).then(() => {
assert.ok(context.calls.length >= 12)
assert.ok(context.calls.every((call) => ['GET', 'POST', 'PUT'].includes(call.method)))
assert.ok(context.calls.every((call) => ['GET', 'POST', 'PUT', 'DELETE'].includes(call.method)))
assert.equal(context.calls.filter((call) => call.channel === 'domestic').length, 1)
assert.ok(context.calls.filter((call) => call.channel === 'overseas').every((call) => call.path.startsWith('/api/platform/v1/')))
const writes = context.calls.filter((call) => call.method !== 'GET')
assert.deepEqual(writes.map((call) => call.method), ['POST', 'PUT', 'PUT', 'PUT'])
assert.ok(writes.every((call) => call.path.includes('/warehouse-admin/20001/pallets')))
assert.ok(writes.some((call) => call.method === 'DELETE' && call.path.includes('/locations/72002')))
assert.ok(writes.some((call) => call.path.includes('/warehouse-admin/20001/pallets')))
assert.ok(writes.some((call) => call.path.includes('/relabel-applications')))
assert.ok(writes.every((call) => call.path.includes('/warehouse-admin/20001/') || call.path.includes('/relabel-applications')))
assert.ok(context.steps.every((step) => step.passed), context.steps.filter((step) => !step.passed).map((step) => `${step.name}: ${step.evidence}`).join('\n'))
})
})
@@ -76,21 +93,29 @@ test('既有仓库场景只写本轮托盘生命周期', () => {
test('覆盖冻结的海外只读列表与有记录才查详情', async () => {
const context = contextFixture()
await run(context)
for (const pathToken of ['/last-leg/orders?', '/relabel-applications?', '/inbound-plans?', '/inventory/summary?', '/inventory/balances?', '/inventory/ledger?', '/warehouse-admin?', '/pallets?']) {
for (const pathToken of ['/last-leg/orders?', '/relabel-applications?', '/inbound-plans?', '/inventory/summary?', '/inventory/balances?', '/inventory/ledger?', '/warehouse-admin?', '/locations?', '/pallets?']) {
assert.ok(context.calls.some((call) => call.channel === 'overseas' && call.path.includes(pathToken)), pathToken)
}
for (const detailToken of ['/orders/30001?', '/relabel-applications/RL-1?', '/inbound-plans/IP-1?', '/balances/60001/positions?', '/warehouse-admin/20001?', '/pallets/70001']) {
assert.ok(context.calls.some((call) => call.path.includes(detailToken)), detailToken)
}
assert.deepEqual(context.context.phase4OverseasWarehouse, { customerId: '10001', warehouseId: '20001', palletId: '71001' })
assert.deepEqual(context.createdResources.phase4OverseasWarehouse, { marker: 'ACC-PHASE4-TEST', customerId: '10001', warehouseId: '20001', palletId: '71001', terminalStatus: 'SCRAPPED' })
for (const call of context.calls.filter((item) => item.path.includes('/inventory/summary?') || item.path.includes('/inventory/balances/export?') || item.path.includes('/inventory/ledger/export?'))) {
const params = new URL(`http://acceptance${call.path}`).searchParams
assert.equal(params.has('page'), false, call.path)
assert.equal(params.has('size'), false, call.path)
}
assert.deepEqual(context.context.phase4OverseasWarehouse, { customerId: '10001', warehouseId: '20001', palletId: '71001', relabelNo: 'FBR-TEST' })
assert.deepEqual(context.createdResources.phase4OverseasWarehouse, {
marker: 'ACC-PHASE4-TEST', customerId: '10001', warehouseId: '20001', palletId: '71001', terminalStatus: 'SCRAPPED',
locationId: '72002', locationTerminalStatus: 'DELETED', relabelNo: 'FBR-TEST', relabelTerminalStatus: 'COMPLETED',
})
})
test('缺少客户时记录缺口,缺少仓库时通过正式接口创建并停用fixture', async () => {
test('缺少客户时明确失败,缺少仓库时通过正式接口创建并停用fixture', async () => {
const noCustomer = contextFixture({ tenants: [] })
await run(noCustomer)
assert.equal(noCustomer.gaps.length, 1)
assert.match(noCustomer.gaps[0][3], /requestId=request-test.*traceId=trace-test/)
assert.equal(noCustomer.gaps.length, 0)
assert.ok(noCustomer.steps.some((step) => step.name === '海外仓验收必须取得ACTIVE客户' && !step.passed))
assert.equal(noCustomer.calls.filter((call) => call.channel === 'overseas').length, 0)
const noWarehouse = contextFixture({ warehouseRecords: [] })
@@ -104,14 +129,17 @@ test('缺少客户时记录缺口,缺少仓库时通过正式接口创建并
test('合同覆盖库存CSV并明确排除暂缓、离仓、PDA与内部路径', () => {
assert.match(source, /inventory\/balances\/export/)
assert.match(source, /inventory\/ledger\/export/)
assert.match(source, /inventory\/summary[^\n]+customerId, page: 1, size: 200/)
assert.match(source, /inventory\/balances\/export[^\n]+customerId, page: 1, size: 200/)
assert.match(source, /inventory\/ledger\/export[^\n]+customerId, page: 1, size: 200/)
assert.match(source, /inventory\/summary[^\n]+\{ customerId \}/)
assert.match(source, /inventory\/balances\/export[^\n]+\{ customerId \}/)
assert.match(source, /inventory\/ledger\/export[^\n]+\{ customerId \}/)
for (const forbidden of ['/cartons', '/products', '/outbound', '/dispatch', '/delivery', '/pda', '/internal']) assert.equal(source.includes(forbidden), false, forbidden)
assert.match(source, /requestId=/)
assert.match(source, /traceId=/)
assert.match(source, /longId\(plan\.sourceOrderId\)/)
assert.match(source, /id\(pallet\.customerId\) === '0'/)
assert.match(source, /换标创建与状态生命周期/)
assert.match(source, /创建本轮换标单/)
assert.match(source, /换标单同幂等键不同请求返回A0409/)
assert.match(source, /创建本轮公共库位/)
assert.match(source, /公共托盘旧版本编辑返回冲突/)
assert.doesNotMatch(source, /deploymentBlocker|addCoverageGap|coverageGap/)
})