import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' import { approvedInboundReady, verifyOrderApprovalReplay } from './orders.mjs' const tenantId = '10001' const orderId = '20001' const taskResponse = { http: 200, body: { code: '200', data: { id: '30001', tenantId, orderId } }, } test('draft inbound id alone does not mean approval outbox has converged', () => { assert.equal(approvedInboundReady({ summary: { inboundId: '40001', mainStatus: 'PREPARING' }, }, taskResponse, tenantId, orderId), false) }) test('measurable inbound without fulfillment task remains pending', () => { assert.equal(approvedInboundReady({ summary: { inboundId: '40001', mainStatus: 'WAIT_MEASURE' }, }, { http: 200, body: { code: 'A0404' } }, tenantId, orderId), false) }) test('approval polling remains pending before the first fulfillment task response', () => { assert.equal(approvedInboundReady({ summary: { inboundId: null, mainStatus: 'PENDING_REVIEW' }, }, null, tenantId, orderId), false) }) test('approval convergence requires matching inbound state, order and customer task identity', () => { const detail = { summary: { inboundId: '40001', mainStatus: 'WAIT_MEASURE' } } assert.equal(approvedInboundReady(detail, taskResponse, tenantId, orderId), true) assert.equal(approvedInboundReady(detail, { http: 200, body: { code: '200', data: { id: '30001', tenantId, orderId: '20002' } }, }, tenantId, orderId), false) assert.equal(approvedInboundReady(detail, { http: 200, body: { code: '200', data: { id: '30001', tenantId: '10002', orderId } }, }, tenantId, orderId), false) }) test('orders acceptance polls approval convergence instead of only inbound id visibility', () => { const source = readFileSync(new URL('orders.mjs', import.meta.url), 'utf8') assert.match(source, /审核后入库单与履约任务异步收敛/) assert.match(source, /approvedInboundReady\(candidate, taskResponse, tenantId, orderId\)/) assert.match(source, /MEASURABLE_STATUSES\.has\(summary\?\.mainStatus\)/) assert.doesNotMatch(source, /MEASURABLE_STATUSES\.has\(summary\?\.processStatus\)/) assert.doesNotMatch(source, /if \(success\(response\) && response\.body\?\.data\?\.summary\?\.inboundId\)/) }) function approvalReplayContext(mutate = () => {}, approvalCode = 'A0409') { const inboundId = '40001'; const inboundCode = 'AT-APPROVAL' const state = { order: { order: { id: orderId, status: 'APPROVED', inboundCode, version: 2 }, cargoes: [], parties: [], internalNotes: [], statusHistory: [{ id: 'history-order', action: 'APPROVED' }] }, task: { id: '30001', tenantId, orderId, status: 'WAIT_MEASURE' }, inbound: { summary: { id: inboundId, tenantId, inboundCode, version: 2, processStatus: 'WAIT_MEASURE' }, statusHistory: [{ id: 'history-inbound', action: 'ORDER_APPROVED' }], files: [], palletLocations: [] }, rows: [{ id: inboundId, tenantId, orderId, inboundCode }], } const calls = []; const checks = [] const context = { marker: 'APPROVAL-REPLAY', createdResources: { orders: {} }, request: async (method, requestPath, body, token, headers) => { calls.push({ method, requestPath, body, token, headers }) if (method === 'POST') { assert.match(requestPath, /\/orders\/first-leg\/20001\/approve\?tenantId=10001$/) assert.equal(body, undefined); assert.equal(headers, undefined) mutate(state) return { http: 200, body: { code: approvalCode }, requestId: 'repeat-approve', traceId: 'trace-approve' } } let data if (requestPath.includes('/orders/first-leg/')) data = state.order else if (requestPath.includes('/tasks/by-order/')) data = state.task else if (requestPath.startsWith(`/api/platform/inbounds/${inboundId}?`)) data = state.inbound else if (requestPath.startsWith('/api/platform/inbounds?')) data = { records: state.rows, page: 1, size: 100, total: state.rows.length, pages: 1, } else throw new Error(`unexpected ${method} ${requestPath}`) return { http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'read', traceId: 'trace' } }, apiSuccess: (_module, _name, response) => { assert.equal(response.http, 200); assert.equal(response.body.code, '200'); return response.body.data }, assert: (_module, name, _response, passed) => { checks.push({ name, passed }); assert.ok(passed, name) }, } return { context, calls, checks, input: { tenantId, orderId, inboundId, inboundCode } } } test('repeated approval requires A0409 and compares order, task, inbound identity, histories and unique count', async () => { const fixture = approvalReplayContext() const result = await verifyOrderApprovalReplay(fixture.context, fixture.input) assert.equal(result.rejected, true) assert.equal(result.unchanged, true) assert.equal(result.inboundCount, 1) assert.equal(result.orderHistoryCount, 1) assert.equal(result.inboundHistoryCount, 1) assert.equal(result.beforeDigest, result.afterDigest) assert.equal(fixture.calls.filter(call => call.method === 'POST').length, 1) assert.equal(fixture.context.createdResources.orders.approvalReplay, result) assert.equal(Object.hasOwn(result, 'order'), false) assert.match(result.beforeDigest, /^[a-f0-9]{64}$/) }) for (const [name, mutate] of [ ['extra approval history', state => state.order.statusHistory.push({ id: 'repeat-history', action: 'APPROVED' })], ['changed inbound version', state => state.inbound.summary.version += 1], ['extra inbound history', state => state.inbound.statusHistory.push({ id: 'repeat-inbound-history' })], ['new fulfillment task', state => { state.task.id = '30002' }], ['duplicate source inbound', state => state.rows.push({ ...state.rows[0], id: '40002' })], ]) { test(`approval replay detects ${name} instead of accepting unchanged response status`, async () => { const fixture = approvalReplayContext(mutate) await assert.rejects(verifyOrderApprovalReplay(fixture.context, fixture.input), /零漂移|唯一入库事实有效/) }) } test('successful repeated approval is not accepted as a substitute for the formal conflict contract', async () => { const fixture = approvalReplayContext(() => {}, '200') await assert.rejects(verifyOrderApprovalReplay(fixture.context, fixture.input), /再次审核明确A0409/) })