206 lines
11 KiB
JavaScript
206 lines
11 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import test from 'node:test'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { run, selectCurrentInboundPlan } from './phase4-overseas-finance-cross-region.mjs'
|
|
|
|
const source = readFileSync(fileURLToPath(new URL('./phase4-overseas-finance-cross-region.mjs', import.meta.url)), 'utf8')
|
|
|
|
test('current plan on second page is used instead of historical first-page plans', async () => {
|
|
const context = fixture(), request = context.request.bind(context), pages = []
|
|
context.request = async (method, path, ...args) => {
|
|
if (path.startsWith('/api/platform/v1/last-leg/inbound-plans?')) {
|
|
pages.push(path)
|
|
if (path.includes('page=1&')) return response({ records: [{ planId: '9999', planNo: 'OLD', customerId: '2001' }], size: 200, total: 201 })
|
|
}
|
|
return request(method, path, ...args)
|
|
}
|
|
await run(context)
|
|
assert.equal(pages.length, 2)
|
|
assert.ok(context.steps.find(step => step.name === '海外入仓费用关联真实计划')?.passed)
|
|
assert.equal(context.calls.some(call => call.method === 'POST' && call.body?.businessId === '9999'), false)
|
|
})
|
|
|
|
test('inbound fees cannot select historical plans or another customer under a fixed account', () => {
|
|
const current = { planId: '4001', planNo: 'CURRENT', customerId: '2001' }
|
|
const old = { planId: '3999', planNo: 'OLD', customerId: '2001' }
|
|
const pda = { marker: 'RUN', customerId: '2001', generatedPlanIds: ['4001'] }
|
|
assert.equal(selectCurrentInboundPlan([old, current], pda, 'RUN'), current)
|
|
assert.equal(selectCurrentInboundPlan([old], pda, 'RUN'), null)
|
|
assert.equal(selectCurrentInboundPlan([current], pda, 'OTHER'), null)
|
|
assert.equal(selectCurrentInboundPlan([{ ...current, customerId: '2002' }], pda, 'RUN'), null)
|
|
assert.equal(selectCurrentInboundPlan([current], { ...pda, generatedPlanIds: [] }, 'RUN'), null)
|
|
})
|
|
|
|
function response(data, code = '200', http = 200, region) {
|
|
return {
|
|
http,
|
|
body: { code, data: region ? { status: '运行中', region } : data },
|
|
requestId: `req-${code}-${http}`,
|
|
traceId: `trace-${code}-${http}`,
|
|
}
|
|
}
|
|
|
|
function fixture() {
|
|
const marker = 'ACC-P4-FIN-XREGION'
|
|
const calls = []
|
|
const steps = []
|
|
const commands = new Map()
|
|
const fees = new Map()
|
|
let sequence = 7000
|
|
const businessRows = {
|
|
'/api/platform/v1/last-leg/overseas-procurements': { id: '3001', orderNo: 'PO-1', customerId: '1101' },
|
|
'/api/platform/v1/last-leg/us-truck-orders': { id: '3002', orderNo: 'TK-1', customerId: '1201' },
|
|
'/api/platform/v1/last-leg/local-looseboard-orders': { id: '3003', orderNo: 'LB-1', customerId: '1301' },
|
|
}
|
|
const context = {
|
|
marker,
|
|
steps,
|
|
scenarios: [],
|
|
calls,
|
|
createdResources: {
|
|
merchantQualificationFixture: { tenantId: '1999' },
|
|
phase4OverseasBusiness: {
|
|
purchase: { orderNo: 'PO-1', customerId: '1101' },
|
|
truck: { orderNo: 'TK-1', customerId: '1201' },
|
|
looseboard: { orderNo: 'LB-1', customerId: '1301' },
|
|
},
|
|
phase4OverseasPda: { marker, generatedPlanIds: ['4001'], customerId: '2001', overseasWarehouseId: '9001' },
|
|
},
|
|
async request(...args) {
|
|
const route = /\/(?:last-leg|overseas-warehouse-resources)(?:\/|\?|$)/.test(String(args[1]));
|
|
const handler = route ? async function(method,path,body,_token,headers = {}) {
|
|
calls.push({ region: 'OVERSEAS', method, path, body, headers })
|
|
if (path === '/internal/finance/event-operations/outbox') return response(null, '404', 404, 'OVERSEAS')
|
|
for (const [base, row] of Object.entries(businessRows)) {
|
|
if (method === 'GET' && path.startsWith(`${base}?`)) return response({ records: [row] })
|
|
}
|
|
if (method === 'GET' && path.startsWith('/api/platform/v1/last-leg/inbound-plans?')) {
|
|
return response({ records: [{ planId: '4001', planNo: 'IP-1', customerId: '2001', inboundId: '4101', inboundCode: 'IN-1' }] })
|
|
}
|
|
if (method === 'GET' && path.startsWith('/api/platform/v1/last-leg/finance-fee-sources?')) return response([])
|
|
if (method === 'POST' && path === '/api/platform/v1/last-leg/finance-fee-sources') {
|
|
if (body.businessType === 'OVERSEAS_PROCUREMENT') return response(null, 'A0409')
|
|
const key = headers['Idempotency-Key']
|
|
const hash = JSON.stringify(body)
|
|
const existing = commands.get(`CREATE:${key}`)
|
|
if (existing) return existing.hash === hash ? response(existing.fee) : response(null, 'A0409')
|
|
const expectedCustomer = body.businessId === '4001' ? '2001'
|
|
: body.businessId === '3001' ? '1101'
|
|
: body.businessId === '3002' ? '1201'
|
|
: body.businessId === '3003' ? '1301' : body.customerId
|
|
if (body.businessId && body.customerId !== expectedCustomer) return response(null, 'A0400')
|
|
const fee = { ...structuredClone(body), sourceFeeId: String(++sequence), sourceVersion: 1,
|
|
reviewStatus: 'DRAFT', publicationStatus: 'UNPUBLISHED', sourceStatus: 'ACTIVE' }
|
|
commands.set(`CREATE:${key}`, { hash, fee })
|
|
fees.set(fee.sourceFeeId, fee)
|
|
return response(fee)
|
|
}
|
|
const action = path.match(/finance-fee-sources\/(\d+)\/(submit|review)$/)
|
|
if (method === 'POST' && action) {
|
|
const fee = fees.get(action[1])
|
|
const key = body.idempotencyKey
|
|
const scope = `${action[2].toUpperCase()}:${key}`
|
|
const hash = JSON.stringify(body)
|
|
const existing = commands.get(scope)
|
|
if (existing) return existing.hash === hash ? response(existing.fee) : response(null, 'A0409')
|
|
if (action[2] === 'submit') fee.reviewStatus = 'PENDING'
|
|
else { fee.reviewStatus = 'APPROVED'; fee.publicationStatus = 'PUBLISHED' }
|
|
commands.set(scope, { hash, fee: structuredClone(fee) })
|
|
return response(fee)
|
|
}
|
|
throw new Error(`unexpected OVERSEAS request ${method} ${path}`)
|
|
} : async function(method,path,body) {
|
|
calls.push({ region: 'CN', method, path, body })
|
|
if (path === '/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE') {
|
|
return response({ records: [{ id: '1001', tenantName: '客户一' }, { id: '1002', tenantName: '客户二' }] })
|
|
}
|
|
if (path === '/internal/finance/event-operations/outbox') return response(null, '404', 404, 'CN')
|
|
if (path.startsWith('/api/platform/finance/v1/fee-sources?')) {
|
|
const query = new URLSearchParams(path.split('?')[1])
|
|
const customerId = query.get('customerId')
|
|
const keyword = query.get('keyword')
|
|
const rows = [...fees.values()].filter((fee) => fee.customerId === customerId
|
|
&& (fee.inboundCode || fee.businessNo) === keyword)
|
|
.filter((fee) => fee.reviewStatus === 'APPROVED')
|
|
.map((fee) => ({ feeSourceId: String(Number(fee.sourceFeeId) + 10000), sourceSystem: 'LASTLEG',
|
|
sourceType: fee.sourceType, sourceFeeId: fee.sourceFeeId, customerId: fee.customerId }))
|
|
return response(rows)
|
|
}
|
|
throw new Error(`unexpected CN request ${method} ${path}`)
|
|
};
|
|
return handler.apply(this,args);
|
|
},
|
|
|
|
apiSuccess(module, name, result, evidence, options) {
|
|
const passed = result.http === 200 && String(result.body?.code) === '200'
|
|
this.addStep(module, name, result, passed, evidence, options)
|
|
return passed ? result.body.data : null
|
|
},
|
|
assert(module, name, result, passed, evidence, options) {
|
|
this.addStep(module, name, result, Boolean(passed), evidence, options)
|
|
},
|
|
addStep(module, name, result, passed, evidence, options = {}) {
|
|
steps.push({ module, name, result, passed: Boolean(passed), evidence,
|
|
deploymentBlocker: Boolean(options.deploymentBlocker) })
|
|
},
|
|
}
|
|
return context
|
|
}
|
|
|
|
test('module uses only injected real HTTP clients and never fabricates database/event success', () => {
|
|
assert.match(source, /export async function run\(context\)/)
|
|
assert.doesNotMatch(source, /process\.env|https?:\/\/|fetch\(|mysql|SELECT\s|INSERT\s|UPDATE\s/i)
|
|
assert.doesNotMatch(source, /admin-test-env-acceptance|README\.md/)
|
|
for (const token of [
|
|
'OVERSEAS_INBOUND_FEE', 'OVERSEAS_PROCUREMENT_FEE', 'OVERSEAS_TRUCK_FEE',
|
|
'OVERSEAS_LOOSEBOARD_FEE', 'OVERSEAS_WAREHOUSE_FEE',
|
|
'/api/platform/v1/last-leg/finance-fee-sources', '/api/platform/finance/v1/fee-sources',
|
|
'/internal/finance/event-operations/outbox',
|
|
]) assert.ok(source.includes(token), token)
|
|
})
|
|
|
|
test('four generic fee types publish while procurement bypass is rejected', async () => {
|
|
const context = fixture()
|
|
await run(context)
|
|
const registry = context.createdResources.phase4OverseasFinanceCrossRegion
|
|
assert.equal(registry.sources.length, 4)
|
|
assert.deepEqual(new Set(registry.sources.map((item) => item.businessType)), new Set([
|
|
'OVERSEAS_INBOUND', 'US_TRUCK', 'LOCAL_LOOSEBOARD', 'WAREHOUSE_SERVICE',
|
|
]))
|
|
const warehouseCreate = context.calls.find((call) => call.method === 'POST'
|
|
&& call.path === '/api/platform/v1/last-leg/finance-fee-sources'
|
|
&& call.body?.businessType === 'WAREHOUSE_SERVICE')
|
|
assert.ok(warehouseCreate)
|
|
for (const field of ['businessId', 'orderId', 'inboundId', 'inboundCode', 'containerId', 'containerBookingId', 'containerNo']) {
|
|
assert.equal(warehouseCreate.body[field], null, field)
|
|
}
|
|
assert.equal(warehouseCreate.body.customerId, '1999')
|
|
assert.equal(warehouseCreate.body.warehouseId, '9001')
|
|
|
|
for (const [path, customerId] of [
|
|
['/api/platform/v1/last-leg/overseas-procurements', '1101'],
|
|
['/api/platform/v1/last-leg/us-truck-orders', '1201'],
|
|
['/api/platform/v1/last-leg/local-looseboard-orders', '1301'],
|
|
]) {
|
|
const lookup = context.calls.find((call) => call.method === 'GET' && call.path.startsWith(`${path}?`))
|
|
assert.equal(new URLSearchParams(lookup.path.split('?')[1]).get('customerId'), customerId)
|
|
}
|
|
|
|
const creates = context.calls.filter((call) => call.method === 'POST'
|
|
&& call.path === '/api/platform/v1/last-leg/finance-fee-sources')
|
|
assert.equal(creates.length, 11)
|
|
assert.ok(context.steps.find((step) => step.name === '费用创建同键异载荷冲突')?.passed)
|
|
assert.ok(context.steps.find((step) => step.name === '海外费用列表不会泄露其他客户来源')?.passed)
|
|
assert.ok(context.steps.find((step) => step.name === '其他客户不能关联本客户业务创建费用')?.passed)
|
|
assert.equal(context.steps.filter((step) => step.name.endsWith('费用经业务Outbox直接投递进入 Finance')).length, 4)
|
|
assert.ok(context.steps.filter((step) => step.name.endsWith('费用经业务Outbox直接投递进入 Finance')).every((step) => step.passed))
|
|
assert.ok(context.steps.find((step) => step.name === '统一Gateway不公开内部事件运维接口')?.passed)
|
|
|
|
assert.deepEqual(context.steps.filter((step) => !step.passed), [])
|
|
assert.ok(context.steps.find(step => step.name === '采购费用禁止绕过履约实扣创建通用来源')?.passed)
|
|
assert.equal(context.scenarios[0].deploymentBlocked, false)
|
|
assert.equal(context.scenarios[0].success, true)
|
|
})
|