import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { fileURLToPath } from 'node:url' import { run } from './phase4-overseas-finance-cross-region.mjs' const source = readFileSync(fileURLToPath(new URL('./phase4-overseas-finance-cross-region.mjs', import.meta.url)), 'utf8') 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: { customerId: '2001', overseasWarehouseId: '9001' }, }, async request(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/event-bridge/status') return response(null, '200', 200, '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}`) }, async requestOverseas(method, path, body, headers = {}) { calls.push({ region: 'OVERSEAS', method, path, body, headers }) if (path === '/internal/event-bridge/status') return response(null, '200', 200, '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') { 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}`) }, 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/event-bridge/status', ]) assert.ok(source.includes(token), token) }) test('five fee types traverse Lastleg lifecycle and reach Finance through real HTTP', async () => { const context = fixture() await run(context) const registry = context.createdResources.phase4OverseasFinanceCrossRegion assert.equal(registry.sources.length, 5) assert.deepEqual(new Set(registry.sources.map((item) => item.businessType)), new Set([ 'OVERSEAS_INBOUND', 'OVERSEAS_PROCUREMENT', '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, 12) 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('费用经跨区链进入 Finance')).length, 5) assert.ok(context.steps.filter((step) => step.name.endsWith('费用经跨区链进入 Finance')).every((step) => step.passed)) assert.ok(context.steps.find((step) => step.name === '国内 Event Bridge HTTP 能力状态可观察')?.passed) assert.ok(context.steps.find((step) => step.name === '海外 Event Bridge HTTP 能力状态可观察')?.passed) assert.deepEqual(context.steps.filter((step) => !step.passed), []) assert.equal(context.scenarios[0].deploymentBlocked, false) assert.equal(context.scenarios[0].success, true) })