import test from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { run } from './phase4-overseas-business.mjs' const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'phase4-overseas-business.mjs'), 'utf8') function response(data, code = '200') { return { http: 200, body: { code, message: code === '200' ? 'success' : 'conflict', data }, requestId: `request-${code}`, traceId: `trace-${code}` } } function page(records = []) { return { records, page: 1, size: 20, total: records.length, pages: records.length ? 1 : 0 } } function blankToNull(value) { return typeof value === 'string' && value.trim() === '' ? null : value } function fixture(options = {}) { const calls = []; const steps = []; const gaps = []; const blockers = [] const marker = 'HTTP-20260824-001' const state = { warehouseStatus: 'ACTIVE', purchase: null, purchaseHash: '', truck: null, truckHash: '', looseboard: null, looseboardHash: '', purchaseHistories: [], truckHistories: [], looseboardHistories: [], purchaseReviews: new Map(), truckReviews: new Map(), truckStatuses: new Map(), looseboardStatuses: new Map(), } const activeCustomers = options.activeCustomers ?? [{ id: '10001', tenantName: '客户甲', status: 'ACTIVE' }] const warehouseOptions = options.warehouseOptions ?? [ { warehouseId: '20001', warehouseCode: 'LAX', warehouseName: '洛杉矶仓', countryCode: 'US' }, ] const credentials = options.credentials === false ? {} : { username: 'merchant', password: 'secret' } function purchaseDetail() { return { summary: state.purchase, remark: state.purchase?.remark ?? null, histories: state.purchaseHistories } } function purchaseCustomer() { return { order: state.purchase, fees: [], progress: [], boxes: [], shipments: [], exceptionSummaries: [] } } function purchasePlatform() { return { customerView: purchaseCustomer(), internalRemark: state.purchase?.remark ?? null, reviewHistory: state.purchaseHistories } } function truckDetail() { return { summary: state.truck, remark: state.truck?.remark ?? null, histories: state.truckHistories } } function looseboardDetail() { return { summary: state.looseboard, remark: state.looseboard?.remark ?? null, histories: state.looseboardHistories } } const context = { marker, calls, steps, gaps, blockers, context: { merchantQualificationFixture: credentials }, createdResources: { merchantQualificationFixture: { tenantId: '10001', marker } }, async request(...args) { const route = /\/(?:last-leg|overseas-warehouse-resources)(?:\/|\?|$)/.test(String(args[1])); const handler = route ? async function(method,requestPath,body,token,headers) { calls.push({ channel: 'overseas', method, path: requestPath, body, tokenPresent: Boolean(token) }) if (requestPath === '/api/merchant/v1/last-leg/overseas-procurements' && method === 'POST') { assert.equal(token, 'merchant-overseas-token') const hash = JSON.stringify(body) if (state.purchase) return hash === state.purchaseHash ? response(purchaseCustomer()) : response(null, 'A0409') state.purchaseHash = hash state.purchase = { id: '30001', orderNo: 'OP1', customerId: '10001', customerName: body.customerName, purchaseWarehouseId: null, purchaseWarehouseName: null, purchaseCategory: body.purchaseCategory, productName: body.productName, productSpecification: body.productSpecification, productUrl: body.productUrl, purchaseQuantity: body.purchaseQuantity, status: 'PENDING_REVIEW', version: 0, remark: body.remark } state.purchaseHistories.push({ operationType: 'CREATE', fromStatus: null, toStatus: 'PENDING_REVIEW', remark: marker }) return response(purchaseCustomer()) } if (requestPath.includes('/customer-options')) return response(state.purchase ? [{ customerId: '10001', customerName: '客户甲' }] : []) if (requestPath.includes('/warehouse-options')) return response(warehouseOptions) if (requestPath.includes('/overseas-procurements?') && method === 'GET') return response(page(state.purchase ? [state.purchase] : [])) if (requestPath.includes('/overseas-procurements/OP1/review?') && method === 'POST') { assert.equal(Number.isInteger(Number(body.version)), true) assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true) const hash = JSON.stringify(body) const replay = state.purchaseReviews.get(body.idempotencyKey) if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409') if (state.purchase.status !== 'PENDING_REVIEW') return response(null, 'A0409') if (Number(body.version) !== state.purchase.version) return response(null, 'A0409') const next = body.decision === 'REJECT' ? 'REJECTED' : 'WAIT_PURCHASE' state.purchase = { ...state.purchase, status: next, version: state.purchase.version + 1 } state.purchaseHistories.push({ operationType: 'REVIEW', fromStatus: 'PENDING_REVIEW', toStatus: next, remark: body.remark }) const result = structuredClone(purchaseDetail()) state.purchaseReviews.set(body.idempotencyKey, { hash, result }) return response(result) } if (requestPath.includes('/overseas-procurements/OP1?') && method === 'GET') return response(purchasePlatform()) if (requestPath.includes('/overseas-procurements/OP1?') && method === 'PATCH') { if (Number(body.version) !== state.purchase.version) return response(null, 'A0409') if (Object.keys(body).some(key => !['version', 'remark'].includes(key))) return response(null, 'A0400') if (!Object.hasOwn(body, 'remark') || blankToNull(body.remark) === state.purchase.remark) return response(purchaseDetail()) state.purchase = { ...state.purchase, remark: blankToNull(body.remark), version: state.purchase.version + 1 } state.purchaseHistories.push({ operationType: 'UPDATE', fromStatus: state.purchase.status, toStatus: state.purchase.status, changeDetail: JSON.stringify({ marker }) }) return response(purchaseDetail()) } if (requestPath.includes('/us-truck-orders?') && method === 'POST') { const hash = JSON.stringify(body) if (state.truck) return hash === state.truckHash ? response(truckDetail()) : response(null, 'A0409') state.truckHash = hash state.truck = { id: '40001', orderNo: 'TK1', customerId: '10001', status: 'PENDING_REVIEW', version: 0, ...body } state.truckHistories.push({ fromStatus: null, toStatus: 'PENDING_REVIEW', remark: marker }) return response(truckDetail()) } if (requestPath.includes('/us-truck-orders?') && method === 'GET') return response(page(state.truck ? [state.truck] : [])) if (requestPath.includes('/us-truck-orders/TK1/review?') && method === 'POST') { assert.equal(Number.isInteger(Number(body.version)), true) assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true) const hash = JSON.stringify(body) const replay = state.truckReviews.get(body.idempotencyKey) if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409') if (state.truck.status !== 'PENDING_REVIEW' || Number(body.version) !== state.truck.version) { return response(null, 'A0409') } const next = body.decision === 'APPROVE' ? 'WAITING_INBOUND' : 'REJECTED' const from = state.truck.status state.truck = { ...state.truck, status: next, version: state.truck.version + 1 } state.truckHistories.push({ fromStatus: from, toStatus: next, remark: body.remark }) const result = structuredClone(truckDetail()) state.truckReviews.set(body.idempotencyKey, { hash, result }) return response(result) } if (requestPath.includes('/us-truck-orders/TK1/status?') && method === 'PATCH') { assert.equal(body.status, 'CANCELLED') assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true) const hash = JSON.stringify(body) const replay = state.truckStatuses.get(body.idempotencyKey) if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409') if (Number(body.version) !== state.truck.version) return response(null, 'A0409') if (!['PENDING_REVIEW', 'WAITING_INBOUND'].includes(state.truck.status)) return response(null, 'A0409') const from = state.truck.status state.truck = { ...state.truck, status: 'CANCELLED', version: state.truck.version + 1 } state.truckHistories.push({ fromStatus: from, toStatus: 'CANCELLED', remark: body.remark }) const result = structuredClone(truckDetail()) state.truckStatuses.set(body.idempotencyKey, { hash, result }) return response(result) } if (requestPath.includes('/us-truck-orders/TK1?') && method === 'GET') return response(truckDetail()) if (requestPath.includes('/us-truck-orders/TK1?') && method === 'PATCH') { if (Number(body.version) !== state.truck.version) return response(null, 'A0409') state.truck = { ...state.truck, ...body, version: state.truck.version + 1 } state.truckHistories.push({ fromStatus: state.truck.status, toStatus: state.truck.status, remark: marker }) return response(truckDetail()) } if (requestPath.includes('/local-looseboard-orders?') && method === 'POST') { const hash = JSON.stringify(body) if (state.looseboard) return hash === state.looseboardHash ? response(looseboardDetail()) : response(null, 'A0409') state.looseboardHash = hash state.looseboard = { id: '50001', orderNo: 'LB1', customerId: '10001', status: 'PENDING_ACCEPTANCE', version: 0, ...body } state.looseboardHistories.push({ operationType: 'CREATE', fromStatus: null, toStatus: 'PENDING_ACCEPTANCE', remark: marker }) return response(looseboardDetail()) } if (requestPath.includes('/local-looseboard-orders?') && method === 'GET') return response(page(state.looseboard ? [state.looseboard] : [])) if (requestPath.includes('/local-looseboard-orders/LB1/status?') && method === 'PATCH') { assert.equal(typeof body.idempotencyKey === 'string' && body.idempotencyKey.length > 0, true) const hash = JSON.stringify(body) const replay = state.looseboardStatuses.get(body.idempotencyKey) if (replay) return replay.hash === hash ? response(replay.result) : response(null, 'A0409') if (Number(body.version) !== state.looseboard.version) return response(null, 'A0409') const allowed = state.looseboard.status === 'PENDING_ACCEPTANCE' && body.status === 'CANCELLED' if (!allowed) return response(null, 'A0409') const from = state.looseboard.status state.looseboard = { ...state.looseboard, status: body.status, version: state.looseboard.version + 1 } state.looseboardHistories.push({ operationType: 'STATUS', fromStatus: from, toStatus: body.status, remark: body.remark }) const result = structuredClone(looseboardDetail()) state.looseboardStatuses.set(body.idempotencyKey, { hash, result }) return response(result) } if (requestPath.includes('/pending-looseboard-demands?') && method === 'GET') return response(page([state.looseboard])) if (requestPath.includes('/local-looseboard-orders/LB1?') && method === 'GET') return response(looseboardDetail()) if (requestPath.includes('/local-looseboard-orders/LB1?') && method === 'PUT') { if (Number(body.version) !== state.looseboard.version) return response(null, 'A0409') state.looseboard = { ...state.looseboard, ...body, version: state.looseboard.version + 1 } state.looseboardHistories.push({ operationType: 'UPDATE', fromStatus: state.looseboard.status, toStatus: state.looseboard.status, changeDetail: JSON.stringify({ marker }) }) return response(looseboardDetail()) } throw new Error(`unexpected ${method} ${requestPath}`) } : async function(method,requestPath,body) { calls.push({ channel: 'domestic', method, path: requestPath, body }) if (requestPath.includes('/iam/tenants?')) return response(page(activeCustomers)) if (requestPath === '/api/auth/login') return response({ accessToken: 'merchant-overseas-token', refreshToken: 'merchant-refresh-token' }) if (requestPath === '/api/auth/logout') { assert.equal(body.refreshToken, 'merchant-refresh-token'); return response(null) } if (method === 'POST' && requestPath === '/api/platform/v1/warehouse-admin') return response('20001') if (method === 'PATCH' && requestPath.includes('/warehouse-admin/20001/status?')) { state.warehouseStatus = 'INACTIVE'; return response(null) } throw new Error(`unexpected domestic ${method} ${requestPath}`) }; return handler.apply(this,args); }, apiSuccess(module, name, result, evidence, stepOptions) { const passed = result.http === 200 && String(result.body?.code) === '200' this.addStep(module, name, result, passed, evidence, stepOptions) return passed ? result.body.data : null }, assert(module, name, result, passed, evidence, stepOptions) { this.addStep(module, name, result, Boolean(passed), evidence, stepOptions) }, addStep(module, name, result, passed, evidence, stepOptions = {}) { const step = { module, name, passed: Boolean(passed), result, evidence, options: stepOptions, requestId: result?.requestId || null, traceId: result?.traceId || null } steps.push(step) if (stepOptions.deploymentBlocker && !step.passed) blockers.push(step) }, addCoverageGap(...args) { gaps.push(args) }, } return context } test('module is context injected, blocks missing prerequisites and excludes forbidden surfaces', () => { assert.match(source, /export async function run\(context\)/) assert.doesNotMatch(source, /https?:\/\/|process\.env|fetch\(/) assert.equal((source.match(/addCoverageGap\(/g) || []).length, 1) assert.match(source, /looseboard-pool-fulfillment/) assert.match(source, /deploymentBlocker: true/) assert.match(source, /contactPhone: '\d+'/) assert.doesNotMatch(source, /contactPhone: '\+[^']+'/) for (const forbidden of ['/outbounds', '/receive-pallets', '/outbound', '/delivery', '/dispatch', '/export']) { assert.equal(source.includes(forbidden), false, forbidden) } assert.doesNotMatch(source, /request\('DELETE'/) }) test('procurement creation review boundaries and transport cancellation paths pass with legal marker terminals', async () => { const context = fixture() await run(context) assert.equal(context.steps.every((step) => step.passed), true, context.steps.filter((step) => !step.passed).map((step) => `${step.name}: ${step.evidence}`).join('\n')) assert.equal(context.gaps.length, 1) assert.equal(context.gaps[0][1], 'looseboard-pool-fulfillment') assert.equal(context.blockers.length, 0) assert.equal(context.calls.filter(call => call.path === '/api/auth/login').length, 1) assert.equal(context.calls.filter(call => call.path === '/api/auth/logout').length, 1) const procurementWrites = context.calls.filter((call) => call.path.includes('/overseas-procurements') && call.method !== 'GET') assert.deepEqual(procurementWrites.map((call) => call.method), ['POST', 'POST', 'POST', 'PATCH', 'PATCH', 'PATCH', 'PATCH', 'PATCH', 'POST', 'POST', 'POST', 'POST']) assert.equal(procurementWrites.filter((call) => call.method === 'PATCH').length, 5) assert.ok(procurementWrites.some((call) => call.method === 'PATCH' && call.body.remark === null)) const procurementReviews = procurementWrites.filter((call) => call.path.includes('/review?')) assert.equal(procurementReviews.length, 4) assert.ok(procurementReviews.every((call) => Number.isInteger(Number(call.body.version)) && typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0)) assert.equal(new Set(procurementReviews.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1) assert.notEqual(procurementReviews[3].body.idempotencyKey, procurementReviews[0].body.idempotencyKey) const truckCalls = context.calls.filter((call) => call.path.includes('/us-truck-orders')) assert.equal(truckCalls.filter((call) => call.method === 'POST').length, 7) assert.equal(truckCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1) const truckReviews = truckCalls.filter((call) => call.method === 'POST' && call.path.includes('/review?')) assert.equal(truckReviews.length, 4) assert.ok(truckReviews.every((call) => Number.isInteger(Number(call.body.version)) && typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0)) assert.equal(new Set(truckReviews.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1) assert.notEqual(truckReviews[3].body.idempotencyKey, truckReviews[0].body.idempotencyKey) const truckStatuses = truckCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?')) assert.equal(truckStatuses.length, 1) assert.ok(truckStatuses.every((call) => call.body.status === 'CANCELLED' && typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0)) const looseboardCalls = context.calls.filter((call) => call.path.includes('/local-looseboard-orders')) assert.equal(looseboardCalls.filter((call) => call.method === 'POST').length, 3) assert.equal(looseboardCalls.filter((call) => call.method === 'GET' && call.path.includes('page=1')).length, 1) assert.equal(looseboardCalls.filter((call) => call.method === 'PUT').length, 2) const looseboardStatuses = looseboardCalls.filter((call) => call.method === 'PATCH' && call.path.includes('/status?')) assert.equal(looseboardStatuses.length, 5) assert.ok(looseboardStatuses.every((call) => typeof call.body.idempotencyKey === 'string' && call.body.idempotencyKey.length > 0)) assert.equal(new Set(looseboardStatuses.slice(0, 3).map((call) => call.body.idempotencyKey)).size, 1) assert.equal(new Set(looseboardStatuses.slice(3).map((call) => call.body.idempotencyKey)).size, 2) assert.ok(looseboardStatuses.slice(3).every((call) => call.body.idempotencyKey !== looseboardStatuses[0].body.idempotencyKey)) const registry = context.createdResources.phase4OverseasBusiness assert.equal(registry.purchase.finalStatus, 'REJECTED') assert.equal(registry.purchase.retained, false) assert.equal(registry.truck.finalStatus, 'CANCELLED') assert.equal(registry.truck.retained, false) assert.equal(registry.looseboard.finalStatus, 'CANCELLED') assert.equal(registry.looseboard.retained, false) assert.equal(registry.partnerWarehouse.finalStatus, 'INACTIVE') assert.equal(registry.partnerWarehouse.retained, false) }) test('old versions and reverse transitions are exact A0409 conflicts with trace evidence', async () => { const context = fixture() await run(context) const conflictSteps = context.steps.filter((step) => /A0409|异载荷|逆向/.test(step.name)) assert.ok(conflictSteps.length >= 7) assert.ok(conflictSteps.every((step) => step.passed)) assert.ok(conflictSteps.every((step) => step.requestId && step.traceId)) assert.ok(conflictSteps.filter((step) => step.name.includes('旧 version')).every((step) => step.result.body.code === 'A0409')) assert.ok(context.steps.filter((step) => /流水/.test(step.name)).every((step) => step.passed)) }) test('missing merchant fixture is an explicit deployment blocker and never a coverage gap', async () => { const context = fixture({ credentials: false }) await run(context) assert.equal(context.gaps.length, 0) assert.ok(context.blockers.some((step) => step.name === '采购客户登录前置完整')) assert.equal(context.calls.some((call) => call.path.includes('/overseas-procurements')), false) assert.equal(context.calls.some((call) => call.path.includes('/us-truck-orders')), false) assert.equal(context.calls.some((call) => call.path.includes('/local-looseboard-orders')), false) }) test('fixture customer must itself be active and cannot borrow another customer identity', async () => { const context = fixture({ activeCustomers: [{ id: '10002', tenantName: '其他客户', status: 'ACTIVE' }] }) await run(context) assert.equal(context.gaps.length, 0) assert.ok(context.blockers.some((step) => step.name === '本轮资格客户属于 ACTIVE 候选')) assert.equal(context.calls.some((call) => call.path === '/api/auth/login'), false) assert.equal(context.calls.some((call) => call.path.includes('/overseas-procurements')), false) }) test('candidate blocker still rejects partial procurement and retires partner warehouse', async () => { const context = fixture({ warehouseOptions: [] }) await run(context) assert.equal(context.gaps.length, 0) assert.ok(context.blockers.some((step) => step.name === '采购仓候选存在 ACTIVE 仓')) const registry = context.createdResources.phase4OverseasBusiness assert.equal(registry.purchase.finalStatus, 'REJECTED') assert.equal(registry.purchase.retained, false) assert.equal(registry.partnerWarehouse.finalStatus, 'INACTIVE') assert.equal(registry.partnerWarehouse.retained, false) assert.equal(context.calls.some((call) => call.path.includes('/us-truck-orders')), false) assert.equal(context.calls.some((call) => call.path.includes('/local-looseboard-orders')), false) }) test('contract locks candidates three-state idempotency histories legal terminals and trace evidence', () => { for (const token of ['/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE', '/overseas-procurements/customer-options?limit=200', '/overseas-procurements/warehouse-options', '/api/merchant/v1/last-leg/overseas-procurements', '采购备注显式清空', '缺失备注不修改且版本不漂移', '同幂等键同载荷精确重放', '同幂等键异载荷返回冲突', '旧 version 返回 A0409', '状态正向推进', '状态逆向回退返回冲突', '流水覆盖', "status: 'CANCELLED'", '/review', 'truck-review-approve', 'looseboard-status-dispatched', 'requestId=', 'traceId=', 'createdResources.phase4OverseasBusiness']) assert.ok(source.includes(token), token) })