import test from 'node:test' import assert from 'node:assert/strict' import { verifyProcurementSample } from './procurement-sample-fulfillment.mjs' function fixture() { const events = [] const context = { marker: 'PROC-TEST', token: 'admin-token', createdResources: {}, async checkpoint() { events.push('checkpoint') }, async request(method, path) { events.push(`${method} ${path}`); throw new Error('network outcome unknown') } } const inputs = { marker: context.marker, customerId: '101', warehouseId: '201', customerName: 'QA', customerToken: 'customer-token', async upload() { throw new Error('must not upload yet') }, async readWallet() { return { actualBalance: '10.00', availableBalance: '10.00', frozenBalance: '0.00', creditUsed: '0.00' } } } return { context, inputs, events } } test('missing owned fixture is rejected before any HTTP', async () => { const { context, inputs, events } = fixture() inputs.marker = 'other-run' await assert.rejects(verifyProcurementSample(context, inputs), /本轮独立客户/) assert.deepEqual(events, []) }) test('insufficient independent cash stops before creating an order', async () => { const { context, inputs, events } = fixture() inputs.readWallet = async () => ({ actualBalance: '1.00', availableBalance: '1.00', frozenBalance: '0.00', creditUsed: '0.00' }) await assert.rejects(verifyProcurementSample(context, inputs), /现金余额/) assert.equal(events.some(event => event.startsWith('POST')), false) assert.equal(context.createdResources.procurementSample.status, 'FAILED') }) test('unknown create outcome is persisted and never automatically retried', async () => { const { context, inputs, events } = fixture() await assert.rejects(verifyProcurementSample(context, inputs), /network outcome unknown/) const proof = context.createdResources.procurementSample assert.equal(proof.requests.length, 1) assert.equal(proof.requests[0].status, 'UNKNOWN_RESULT') assert.ok(proof.requests[0].body.idempotencyKey) assert.equal(events.filter(event => event.startsWith('POST')).length, 1) const write = events.findIndex(event => event.startsWith('POST')) assert.equal(events[write - 1], 'checkpoint') assert.equal(JSON.stringify(proof).includes('customer-token'), false) await assert.rejects(verifyProcurementSample(context, inputs), /不自动重跑/) }) test('failure to persist the request prevents network dispatch', async () => { const { context, inputs, events } = fixture() context.checkpoint = async () => { throw new Error('disk write failed') } await assert.rejects(verifyProcurementSample(context, inputs), /disk write failed/) assert.equal(events.length, 0) }) function completeFixture(fault = '') { const { context, inputs } = fixture() const calls = [], seen = new Map() let spent = 0, frozen = 0, creations = 0 const order = { orderNo: 'CG-TEST-1', customerId: '101', status: 'PENDING_REVIEW', version: 0, updatedAt: '2026-10-22T12:00:00' } const fees = [], physical = { receipts: [], cartons: [], packingVersion: 0 } const transport = { overseasLabels: { version: 0 }, domesticLabels: { version: 0 }, shipments: [] } const detail = () => structuredClone({ customerView: { order, fees }, physical, transport, fees }) inputs.upload = async () => ({ fileId: '501' }) inputs.readWallet = async () => ({ actualBalance: ((1000 - spent) / 100).toFixed(2), availableBalance: ((1000 - spent - frozen) / 100).toFixed(2), frozenBalance: (frozen / 100).toFixed(2), creditUsed: '0.00' }) context.request = async (method, path, body) => { calls.push({ method, path, body: structuredClone(body) }) const reply = data => ({ http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'req-test', traceId: 'trace-test' }) if (method === 'GET') { if (path.startsWith('/api/merchant/')) return reply({ order: { ...order, ...(fault === 'customer final order' ? { orderNo: 'OTHER' } : {}) } }) const result = detail() if (fault === 'aggregate customer') result.customerView.order.customerId = '999' if (fault === 'aggregate order') result.customerView.order.orderNo = 'OTHER' if (fault === 'aggregate version') result.customerView.order.version = null return reply(result) } if (path === '/api/merchant/v1/last-leg/overseas-procurements') { creations++ return reply({ order: { ...order, ...(creations === 2 && fault === 'create replay customer' ? { customerId: '999' } : {}) } }) } assert.equal(method, 'POST') const action = path.split('/CG-TEST-1/')[1]?.split('?')[0] assert.ok(action, path) if (seen.has(body.idempotencyKey)) { assert.deepEqual(body, seen.get(body.idempotencyKey)) if (fault === 'replay deducts' && action === 'purchase') spent += 100 return reply(detail()) } assert.equal(body.version, order.version) seen.set(body.idempotencyKey, structuredClone(body)) switch (action) { case 'review': order.status = 'APPROVED'; break case 'purchase': spent += 100 fees.push({ active: true, purpose: 'SAMPLE_PAYMENT', walletStatus: 'SETTLED', sourceLinked: true, walletBusinessNo: 'PAY-1' }) break case 'overseas-receipts': case 'domestic-receipts': physical.receipts.push({ id: String(601 + physical.receipts.length), leg: body.leg, active: true, goodQuantity: body.goodQuantity }); break case 'overseas-receipts/complete': case 'domestic-receipts/complete': break case 'packing': physical.cartons = body.cartons.map((row, index) => ({ ...row, id: String(701 + index) })); physical.packingVersion++; break case 'packing/confirm': assert.equal(body.packingVersion, physical.packingVersion); break case 'labels': transport[body.leg === 'OVERSEAS' ? 'overseasLabels' : 'domesticLabels'].version++; break case 'international-estimate': frozen = 200; fees.push({ active: true, purpose: 'INTERNATIONAL_ESTIMATE', walletStatus: 'FROZEN' }); break case 'labels/apply': break case 'overseas-shipment': case 'customer-shipment': transport.shipments.push({ leg: body.leg }) if (action === 'customer-shipment') order.status = 'SHIPPED_TO_CUSTOMER' break case 'charges/confirm': spent += 150; frozen = 0 fees.find(row => row.purpose === 'INTERNATIONAL_ESTIMATE').walletStatus = 'RELEASED' fees.push({ active: true, purpose: 'INTERNATIONAL_FREIGHT', walletStatus: 'SETTLED', sourceLinked: true, walletBusinessNo: 'PAY-2' }) break default: assert.fail(`Unexpected operation ${action}`) } order.version++ return reply(detail()) } return { context, inputs, calls, seen, physical, transport } } test('sample procurement traverses receipt, packing, both shipments and exact wallet settlement with same-key replays', async () => { const h = completeFixture() const proof = await verifyProcurementSample(h.context, h.inputs) assert.equal(proof.status, 'PASSED') assert.equal(proof.settledAmountCny, '2.50') assert.deepEqual(proof.walletBusinessNos, ['PAY-1', 'PAY-2']) assert.equal(h.physical.receipts.length, 4) assert.equal(h.physical.cartons.length, 2) assert.deepEqual(h.transport.shipments.map(row => row.leg), ['OVERSEAS', 'DOMESTIC']) assert.equal(proof.walletChecks.length, 4) assert.ok(proof.checks.every(check => check.passed)) }) for (const fault of ['aggregate customer', 'aggregate order', 'aggregate version', 'create replay customer', 'customer final order', 'replay deducts']) { test(`procurement stops rather than passing ${fault}`, async () => { const h = completeFixture(fault) await assert.rejects(verifyProcurementSample(h.context, h.inputs)) assert.equal(h.context.createdResources.procurementSample.status, 'FAILED') if (fault.startsWith('aggregate') || fault === 'create replay customer') assert.equal(h.seen.size, 0) if (fault === 'replay deducts') assert.equal(h.physical.receipts.length, 0) }) }