109 lines
8.0 KiB
JavaScript
109 lines
8.0 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { verifyFleetFeeCoverage } from './fleet-fee-coverage-acceptance.mjs'
|
|
|
|
function fixture(mode = 'FULL', fault = '') {
|
|
const source = { marker: 'CURRENT-TEST', mode, dispatchNo: 'D-CURRENT', customerId: '900719925474099301',
|
|
transferBody: { loadingType: 'PALLET', appointmentAccount: 'SELF', fees: [
|
|
{ direction: 'RECEIVABLE', feeType: 'DELIVERY_FEE', feeName: '派送费', amount: 17.5, currency: 'USD' },
|
|
] } }
|
|
const cargo = [1, 2].map(n => ({ dispatchItemId: `item-${n}`, warehousePalletId: `pallet-${n}`, orderId: `order-${n}`,
|
|
sourceRegion: 'FIRSTLEG', sourceInboundId: `90071992547409940${n}`, inboundCode: `IN-${n}`, packages: 2 }))
|
|
const fees = cargo.map((row, index) => ({ financeFeeId: `90071992547409950${index}`, sourceVersion: 1, sourceHash: `fee-hash-${index}`,
|
|
sourceInboundId: row.sourceInboundId, customerId: source.customerId, sourceType: 'INBOUND_FEE', currency: 'CNY', amount: 200, status: 'AVAILABLE' }))
|
|
if (fault === 'foreign-customer') fees[0].customerId = 'different-customer'
|
|
if (fault === 'missing-source') fees.splice(1)
|
|
const hash = 'a'.repeat(64), calls = [], assertions = []
|
|
let savedBody, replayed = false, dirtyOutbound = false
|
|
const response = (data, message = '') => ({ http: 200, body: { code: message ? 'A0409' : '200', message, data }, requestId: 'req-test', traceId: 'trace-test' })
|
|
const context = { marker: source.marker, createdResources: { fleetFeeCoverageCases: [source] },
|
|
assert(module, name, res, passed, evidence, options) { assertions.push({ name, passed, options }) },
|
|
async request(method, path, body) {
|
|
calls.push({ method, path, body })
|
|
if (method === 'GET' && path === '/api/platform/v1/last-leg/dispatches/D-CURRENT')
|
|
return response({ dispatchNo: source.dispatchNo, pickedPackageCount: 0, shippedPackageCount: 0,
|
|
items: cargo.map((row, index) => ({ id: row.dispatchItemId, packageCount: row.packages,
|
|
destinationWarehouseCode: `FBA-${index + 1}`, fbaWarehouseCode: `ORIGINAL-FBA-${index + 1}` })) })
|
|
if (method === 'GET' && path.endsWith('/fee-coverage')) return response({ dispatchNo: source.dispatchNo, sourceComplete: true,
|
|
cargoHash: hash, cargo, feeSources: fees.map(row => fault === 'mutated-original' && savedBody ? { ...row, amount: 201 } : row) })
|
|
if (method === 'GET' && path.includes('/outbound-orders?')) {
|
|
const params = new URL(path, 'https://test.invalid').searchParams
|
|
assert.equal(params.get('customerId'), source.customerId)
|
|
assert.equal(params.has('dispatchNo'), false, 'outbound list does not support dispatchNo filtering')
|
|
const rows = dirtyOutbound ? [{ outboundNo: 'OUT-WRONG', dispatchNo: source.dispatchNo }] : [{ outboundNo: 'OUT-OTHER', dispatchNo: 'DS-OTHER' }]
|
|
return response({ records: rows, total: rows.length, page: 1, size: 200 })
|
|
}
|
|
if (method === 'GET' && path === '/api/platform/v1/last-leg/outbound-orders/OUT-CURRENT') return response({
|
|
outbound: { outboundNo: 'OUT-CURRENT', dispatchNo: source.dispatchNo, status: 'WAIT_OUTBOUND' },
|
|
deliveries: [1, 2].map(n => ({ deliveryNo: `DEL-${n}`, status: 'WAIT_DELIVERY' })),
|
|
})
|
|
if (method === 'GET' && path.endsWith('/external-freight')) return response({ snapshotHash: 'b'.repeat(64), quotes: [], requiresDeliveryFee: true })
|
|
if (method === 'POST' && path.endsWith('/transfer-delivery')) {
|
|
if (body.feeCoverageCargoHash !== hash) {
|
|
if (fault === 'failed-command-writes') dirtyOutbound = true
|
|
return response(null, '原费用对应货物已变化,请重新核对覆盖范围')
|
|
}
|
|
if (body.feeCoverage[0].sourceVersion !== 1) return response(null, '原费用版本已变化,请重新确认服务覆盖范围')
|
|
if (mode === 'FULL' && body.fees.length) return response(null, '本次货物的该服务已由原费用覆盖,请勿重复登记应收')
|
|
if (mode === 'PARTIAL' && !body.fees.length) return response(null, '尚未有约定运费的货物须填写派送费')
|
|
replayed = Boolean(savedBody)
|
|
if (savedBody) assert.deepEqual(body, savedBody)
|
|
savedBody = structuredClone(body)
|
|
return response({ dispatchNo: source.dispatchNo, outboundNo: 'OUT-CURRENT', outboundStatus: 'WAIT_OUTBOUND', replayed,
|
|
deliveries: [1, 2].map(n => ({ deliveryNo: `DEL-${n}`, deliveryStatus: 'WAIT_DELIVERY' })) })
|
|
}
|
|
const detail = path.match(/^\/api\/platform\/v1\/last-leg\/delivery-orders\/DEL-([12])\?customerId=900719925474099301$/)
|
|
assert.ok(detail, `Unexpected ${method} ${path}`)
|
|
const index = Number(detail[1]) - 1
|
|
const selected = savedBody.feeCoverage.filter(selection => selection.financeFeeId === fees[index].financeFeeId)
|
|
const references = selected.map(selection => ({ source: fees[index], services: selection.services }))
|
|
const shouldCharge = mode === 'PARTIAL' && (fault === 'charge-covered-destination' ? index === 0 : index === 1)
|
|
const newFee = { feeDirection: 'RECEIVABLE', feeType: 'DELIVERY_FEE', currency: 'USD', amount: 17.5, sourceStatus: 'ACTIVE' }
|
|
return response({ order: { dispatchNo: source.dispatchNo, deliveryNo: `DEL-${index + 1}`, destinationWarehouseCode: `FBA-${index + 1}` },
|
|
fees: shouldCharge || (fault === 'duplicate-new-fee' && index === 0) || (fault === 'replay-adds-fee' && replayed) ? [newFee] : [],
|
|
feeCoverage: { cargoHash: hash, operatorId: 'op-test', confirmedAt: replayed && fault === 'rewrite-confirmation' ? '2026-10-03T02:00:00Z' : '2026-10-03T01:00:00Z',
|
|
cargo: fault === 'wrong-cargo' ? [cargo[0]] : [cargo[index]], references: fault === 'missing-reference' ? [] : references } })
|
|
},
|
|
}
|
|
return { source, context, calls, assertions, clearFault() { fault = '' } }
|
|
}
|
|
|
|
test('persisted reconciliation reads exact original documents and only replays the original successful command', async () => {
|
|
const f = fixture('FULL', 'wrong-cargo')
|
|
await assert.rejects(() => verifyFleetFeeCoverage(f.context, 'coverage', f.source))
|
|
assert.equal(f.source.proof.outboundNo, 'OUT-CURRENT')
|
|
const originalKey = f.source.proof.confirmationRequest.idempotencyKey
|
|
const before = f.calls.length
|
|
f.clearFault()
|
|
const proof = await verifyFleetFeeCoverage(f.context, 'coverage', f.source, { reconcilePersisted: true })
|
|
assert.equal(proof.status, 'VERIFIED')
|
|
const commands = f.calls.slice(before).filter(row => row.method === 'POST')
|
|
assert.ok(commands.length > 0)
|
|
assert.ok(commands.every(row => row.body.idempotencyKey === originalKey))
|
|
assert.ok(f.calls.slice(before).some(row => row.method === 'GET' && row.path.endsWith('/outbound-orders/OUT-CURRENT')))
|
|
})
|
|
|
|
for (const mode of ['FULL', 'PARTIAL']) {
|
|
test(`${mode} fee coverage helper checks source scope, pre-write conflicts, each destination and replay`, async () => {
|
|
const f = fixture(mode)
|
|
const proof = await verifyFleetFeeCoverage(f.context, 'coverage', f.source)
|
|
assert.equal(proof.status, 'VERIFIED')
|
|
assert.equal(proof.newReceivable, mode === 'FULL' ? 0 : 17.5)
|
|
assert.deepEqual(proof.deliveryNos, ['DEL-1', 'DEL-2'])
|
|
assert.ok(f.assertions.every(row => row.passed && row.options.deploymentBlocker))
|
|
assert.ok(f.calls.every(call => !/cancel|receipt|departure/.test(call.path)))
|
|
await assert.rejects(() => verifyFleetFeeCoverage(f.context, 'coverage', f.source), /未执行/)
|
|
})
|
|
}
|
|
|
|
for (const fault of ['foreign-customer', 'missing-source', 'failed-command-writes', 'wrong-cargo', 'missing-reference',
|
|
'duplicate-new-fee', 'rewrite-confirmation', 'mutated-original', 'charge-covered-destination', 'replay-adds-fee']) {
|
|
test(`fee coverage HTTP proof cannot pass ${fault}`, async () => {
|
|
const f = fixture(fault === 'charge-covered-destination' ? 'PARTIAL' : 'FULL', fault)
|
|
await assert.rejects(() => verifyFleetFeeCoverage(f.context, 'coverage', f.source))
|
|
assert.equal(f.source.proof.status, 'CHECKING')
|
|
assert.ok(f.assertions.some(row => !row.passed))
|
|
if (['foreign-customer', 'missing-source'].includes(fault)) assert.equal(f.calls.some(row => row.method === 'POST'), false)
|
|
})
|
|
}
|