Files
baiyue-firstleg/scripts/admin-test-env-modules/fleet-company-cost-acceptance-contract.test.mjs
2026-10-27 17:44:41 +08:00

165 lines
13 KiB
JavaScript

import assert from 'node:assert/strict'
import test from 'node:test'
import { run } from './fleet-company-cost-acceptance.mjs'
import { requireReady, READY_COMMIT } from '../fleet-company-cost-test-env.mjs'
const copy = value => value == null ? value : structuredClone(value)
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aH1kAAAAASUVORK5CYII=', 'base64')
function fixture(fault) {
const marker = 'CONTROLLED-COMPANY-COST', calls = [], vehicles = new Map(), records = new Map(), costs = new Map(), submissions = new Map(), commands = new Map(), files = new Map()
const response = (data, code = '200', http = 200) => ({ http, body: { code, data: copy(data) }, requestId: 'controlled-request', traceId: 'controlled-trace' })
let sequence = 0
const history = row => ({ recordVersion: row.cost.recordVersion, action: row.action, snapshot: copy(row.cost) })
function save(row, action) { row.action = action; row.history.unshift(history(row)) }
function current(row) { const { cost } = row; return copy(cost) }
const supplier = { id: '8', tenantId: fault === 'foreign supplier' ? '9' : '0', category: 'TRUCKING', truckingScope: 'OVERSEAS', status: fault === 'inactive supplier' ? 'INACTIVE' : 'ACTIVE' }
vehicles.set('71', { id: '71', plateNumber: 'EXISTING-TEST', region: 'OVERSEAS', ownership: fault === 'external vehicle' ? 'EXTERNAL' : 'OWNED', status: fault === 'inactive vehicle' ? 'INACTIVE' : 'IN_SERVICE', recordVersion: 0 })
const context = { marker, token: 'offline-test-only', createdResources: {}, options: { fixedCompanyCostResources: { supplierId: '8', vehicleId: '71' } },
assert(_module, label, _response, condition) { assert.ok(condition, label) },
apiSuccess(_module, label, result) { assert.equal(result?.http, 200, label); assert.equal(result?.body?.code, '200', label); return result.body.data },
async request(method, raw, body, _token, headers) {
calls.push({ method, raw, body: copy(body), headers: copy(headers) }); const url = new URL(raw, 'https://fixture.invalid'), path = url.pathname
if (method === 'GET' && path === '/api/platform/v1/suppliers/8') return response(supplier)
if (method === 'GET' && path.includes('/master-data/exchange-rates/')) return response({ month: path.split('/').at(-1), status: fault === 'inactive exchange' ? 'INACTIVE' : 'ACTIVE', usdToCnyAverage: '7.123456', recordVersion: 0 })
if (path.endsWith('/fee-pool')) return response(fault === 'customer pollution' && costs.size ? [{ poolItemId: 'LEAK', customerId: '9', amount: 27.31, feeItemName: marker }] : [])
if (path.endsWith('/cost-entries')) return response([])
if (path.endsWith('/customer-accounts/9/summary')) return response({ customerId: '9', currency: 'CNY', totalReceivable: 100, receivedAmount: 30, outstandingReceivable: 70,
pendingBilling: 0, totalCost: 0, paidCost: 0, outstandingPayable: 0, asOf: new Date().toISOString() })
if (path.startsWith('/api/platform/v1/last-leg/master-data/vehicles/')) {
const row = vehicles.get(path.split('/vehicles/')[1].split('/')[0]); assert.ok(row)
assert.equal(method, 'GET', 'shared vehicle masters must remain read-only'); return response(row)
}
const care = path.match(/^\/api\/platform\/v1\/last-leg\/fleet\/vehicles\/71\/records(?:\/(.*))?$/)
if (care) {
const suffix = care[1]
if (!suffix && method === 'POST') {
const recordNo = `VR-${records.size + 1}`, row = { recordNo, vehicleId: '71', vehiclePlate: vehicles.get('71').plateNumber, recordVersion: 0, details: copy(body.details) }
records.set(recordNo, row); return response(row)
}
if (suffix === 'finance-status') {
return response(body.recordNos.map(recordNo => {
const record = records.get(recordNo), row = costs.get(recordNo); assert.ok(record && row)
if (fault === 'delivery exhausted') return { recordNo, status: 'FINANCE_ATTENTION', sourceVersion: row.cost.source.sourceVersion }
if (fault === 'never delivered') return { recordNo, status: 'PENDING_FINANCE', sourceVersion: row.cost.source.sourceVersion }
const cost = current(row)
if (fault === 'wrong source vehicle') cost.source.vehicleId = 'OTHER'
if (fault === 'wrong source version') cost.source.sourceVersion = 999
if (fault === 'wrong conversion') cost.source.amount = 999
if (fault === 'premature paid') cost.status = 'PAID'
return { recordNo, status: cost.status, sourceVersion: cost.source.sourceVersion, submittedRecordVersion: record.recordVersion, finance: cost }
}))
}
const [recordNo, action] = suffix.split('/'), record = records.get(recordNo); assert.ok(record)
if (method === 'GET') return response(record)
if (method === 'PUT') {
if (fault === 'paid source mutable') { record.details = copy(body.details); record.recordVersion++; return response(record) }
return response(null, 'A0409')
}
assert.equal(action, 'finance')
const key = `${recordNo}:${body.idempotencyKey}`
if (submissions.has(key)) return response({ ...submissions.get(key), replayed: true })
let row = costs.get(recordNo), sourceVersion = row ? row.cost.source.sourceVersion + 1 : 1
if (row && row.cost.status !== 'VOIDED') return response(null, 'A0409')
const incident = record.details.incident, rate = incident.currency === 'CNY' ? 1 : 7.123456
const source = { sourceType: record.details.recordType, sourceNo: recordNo, sourceVersion, vehicleId: '71', vehiclePlate: record.vehiclePlate,
supplierId: body.supplierId, feeName: incident.item, businessDate: body.businessDate, originalCurrency: incident.currency,
originalAmount: incident.reportedCost, exchangeRate: rate, exchangeRateMonth: body.businessDate.slice(0, 7),
amount: Math.round(incident.reportedCost * rate * 100) / 100, remark: record.details.remark }
const cost = { costNo: row?.cost.costNo ?? `VC-${++sequence}`, subjectCode: 'PLATFORM_DEFAULT', currency: 'CNY', status: 'PENDING', paidAmount: 0, outstandingAmount: null,
recordVersion: row ? row.cost.recordVersion + 1 : 0, source }
if (!row) row = { cost, payments: [], history: [] }
else row.cost = cost
save(row, 'RECEIVE'); costs.set(recordNo, row)
const result = { recordNo, sourceVersion, submittedRecordVersion: record.recordVersion, replayed: false, status: 'PENDING_FINANCE' }
submissions.set(key, copy(result)); return response(result)
}
const match = path.match(/^\/api\/platform\/finance\/v1\/vehicle-costs\/([^/]+)(?:\/(.*))?$/)
assert.ok(match, `Unexpected ${method} ${raw}`)
const [, no, action] = match, row = [...costs.values()].find(r => r.cost.costNo === no); assert.ok(row)
if (method === 'GET') {
if (!action) return response(current(row))
const list = action === 'payments' ? row.payments : row.history
return response({ records: list, page: 1, size: 100, total: list.length, pages: list.length ? 1 : 0 })
}
const key = `${no}:${action}:${headers?.['Idempotency-Key']}`
assert.ok(headers?.['Idempotency-Key'], 'financial commands require their stable header key')
if (commands.has(key) && !(fault === 'duplicate payment replay' && action === 'payments')) return response(current(row))
if (Number(body.expectedVersion) !== row.cost.recordVersion && !(fault === 'duplicate payment replay' && action === 'payments')) return response(null, 'FIN_409')
if (action === 'approve') { row.cost.status = 'APPROVED'; row.cost.outstandingAmount = row.cost.source.amount; row.cost.recordVersion++; save(row, 'APPROVE') }
else if (action === 'void') {
if (row.cost.paidAmount > 0 && fault !== 'paid void allowed') return response(null, 'FIN_409')
row.cost.status = 'VOIDED'; row.cost.outstandingAmount = null; row.cost.recordVersion++; save(row, 'VOID')
} else if (action === 'payments') {
if (body.amount > row.cost.outstandingAmount && fault !== 'overpayment allowed') return response(null, 'FIN_409')
row.cost.paidAmount = Math.round((row.cost.paidAmount + body.amount) * 100) / 100
row.cost.outstandingAmount = Math.round((row.cost.source.amount - row.cost.paidAmount) * 100) / 100
row.cost.status = row.cost.outstandingAmount === 0 ? 'PAID' : 'PARTIALLY_PAID'; row.cost.recordVersion++
row.payments.push({ paymentNo: `VP-${++sequence}`, currency: 'CNY', amount: body.amount, voucherFileIds: copy(body.voucherFileIds) }); save(row, 'PAY')
if (fault === 'partial money drift') row.cost.outstandingAmount += 0.01
} else throw new Error(`Unexpected action ${action}`)
commands.set(key, true); return response(current(row))
},
async requestMultipart(method, raw, file) {
calls.push({ method, raw, multipart: true }); assert.ok(Buffer.from(file.content).equals(PNG))
if (fault === 'file outage') return response(null, 'FIN_503', 503)
const fileId = id(files.size + 1); files.set(fileId, raw)
return response({ fileId, ownerScope: 'PLATFORM', tenantId: '0', businessType: 'FINANCE_VEHICLE_VOUCHER', contentType: 'image/png', sizeBytes: PNG.length })
},
async requestBinary(method, raw) { calls.push({ method, raw, binary: true }); return { http: 200, contentType: 'image/png', bytes: fault === 'wrong file bytes' ? Buffer.from('wrong') : PNG } },
}
return { context, calls, vehicles, records, costs }
}
const id = value => String(value)
const options = { pollAttempts: 2, pollDelayMs: 0 }
test('independent runner requires explicit service readiness and only the specified TEST profile', () => {
const env = 'C:/Users/BY-OY/.codex/env/chongchong-global/test/acceptance.env'
assert.throws(() => requireReady([], [`--env-file=${env}`]), /就绪/)
assert.throws(() => requireReady([`--ready-commit=${READY_COMMIT}`], [`--env-file=${env.replace('/test/', '/dev/')}`]), /授权目录/)
assert.ok(requireReady([`--ready-commit=${READY_COMMIT}`], [`--env-file=${env}`]).replaceAll('\\', '/').endsWith('/test/acceptance.env'))
})
test('readiness gate accepts only the full production anchor, not old anchors, shorthand or another commit', () => {
const profile = ['--env-file=C:/Users/BY-OY/.codex/env/chongchong-global/test/acceptance.env']
assert.equal(READY_COMMIT, '8700fc34238bc33339351c73c15f3af8de8ce517')
for (const commit of ['a6fd0ba3', 'cb6c54ff', '8700fc34', 'd05ff1cd', `${READY_COMMIT}0`]) {
assert.throws(() => requireReady([`--ready-commit=${commit}`], profile), /就绪/)
}
assert.ok(requireReady([`--ready-commit=${READY_COMMIT}`], profile).replaceAll('\\', '/').endsWith('/test/acceptance.env'))
})
test('company cost verifier exercises CNY/USD, void/rebuild, payment replay and customer isolation without direct transport', async () => {
const { context, calls, vehicles, records, costs } = fixture()
await run(context, options)
const proof = context.createdResources.fleetCompanyCostAcceptance
assert.equal(proof.status, 'VERIFIED'); assert.equal(proof.cleanup, 'REUSED_UNCHANGED'); assert.equal(proof.reusedMasterData, true)
assert.equal(proof.cases.length, 2); assert.equal(records.size, 2)
assert.ok([...costs.values()].every(row => row.cost.status === 'PAID' && row.payments.length === 2))
assert.ok([...vehicles.values()].every(row => row.status === 'IN_SERVICE' && row.recordVersion === 0))
assert.equal(calls.some(call => call.method !== 'GET' && /suppliers|master-data\/vehicles/.test(call.raw)), false)
assert.ok(calls.every(call => call.raw.startsWith('/api/platform/')))
assert.equal(calls.some(call => /cost-entries|customer-accounts|customerId=/.test(call.raw)), false, 'must not borrow another task customer for evidence')
assert.equal(calls.some(call => call.method === 'DELETE'), false)
await assert.rejects(run(context, options), /本轮已存在/)
})
test('missing independent marker blocks before any write', async () => {
const { context, calls } = fixture(); context.marker = ''
await assert.rejects(run(context, options), /独立marker/); assert.equal(calls.length, 0)
})
for (const resources of [undefined, {}, { supplierId: '8' }, { supplierId: '8', vehicleId: '0' }, { supplierId: 8, vehicleId: 71 }]) test(`missing fixed company masters stop before HTTP: ${JSON.stringify(resources)}`, async () => {
const { context, calls } = fixture(); context.options.fixedCompanyCostResources = resources
await assert.rejects(run(context, options), /禁止自动创建主档/)
assert.equal(calls.length, 0)
})
for (const fault of ['inactive exchange', 'delivery exhausted', 'never delivered', 'wrong source vehicle', 'wrong source version', 'wrong conversion', 'premature paid',
'duplicate payment replay', 'overpayment allowed', 'partial money drift', 'paid void allowed', 'paid source mutable', 'customer pollution', 'file outage', 'wrong file bytes',
'foreign supplier', 'inactive supplier', 'external vehicle', 'inactive vehicle']) {
test(`company cost verifier rejects ${fault} and preserves failure evidence`, async () => {
const { context, vehicles, calls } = fixture(fault), before = copy([...vehicles.values()])
await assert.rejects(run(context, options))
const proof = context.createdResources.fleetCompanyCostAcceptance
assert.equal(proof.status, 'FAILED')
assert.ok(proof.error || proof.cleanupError)
assert.deepEqual([...vehicles.values()], before)
assert.equal(calls.some(call => call.method !== 'GET' && /suppliers|master-data\/vehicles/.test(call.raw)), false)
})
}