Files
baiyue-firstleg/scripts/fee-void-recovery-test-env.test.mjs

73 lines
5.3 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { mkdtempSync, existsSync, readFileSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, dirname, basename, resolve } from 'node:path'
import { parseFeeVoidArguments, runFeeVoid } from './fee-void-recovery-test-env.mjs'
const config = () => parseFeeVoidArguments(['--execute', '--environment', 'TEST', '--run-id', 'offline',
'--customer-id', '41', '--control-customer-id', '42', '--warehouse-id', '900'])
const env = { CHONGCHONG_ENV: 'test', CHONGCHONG_ADMIN_USERNAME: 'offline-admin',
CHONGCHONG_ADMIN_PASSWORD: 'private-password', CHONGCHONG_ADMIN_API_BASE_URL: 'https://test.example.invalid/api' }
function harness(outputRoot, failure) {
const calls = []; let fee, finished = false, cleanup = 0
const context = { marker: 'FEE-OFFLINE', async request(method, path, body) {
calls.push({ method, path })
let data
if (path === '/api/auth/login') data = { accessToken: 'private-access', refreshToken: 'private-refresh' }
else if (path === '/api/auth/logout') { if (failure === 'logout') throw Error('LOGOUT_TRANSPORT'); data = {} }
else if (path.includes('/iam/tenants/')) data = { id: path.split('/').at(-1), tenantName: 'Offline customer', status: 'ACTIVE' }
else if (path.includes('/warehouse-admin/')) data = { warehouse: { id: '900', tenantId: '0', status: 'ACTIVE', warehouseType: 'OVERSEAS' } }
else if (path.includes('/fee-pool?')) data = path.includes('customerId=41') && fee?.reviewStatus === 'APPROVED' && (!finished || failure === 'pool')
? [{ customerId: '41', sourceId: '81', sourceVersion: 1, amount: 17.31, currency: 'CNY' }] : []
else if (method === 'GET') data = path.includes('customerId=41') && fee ? [structuredClone(fee)]
: finished && failure === 'isolation' ? [{ customerId: '42', sourceFeeId: '99' }] : []
else if (path.endsWith('/submit')) { fee.reviewStatus = 'PENDING'; data = fee }
else if (path.endsWith('/review')) { Object.assign(fee, { reviewStatus: 'APPROVED', publicationStatus: 'PUBLISHED', sourceStatus: 'ACTIVE' }); data = fee }
else if (path.endsWith('/void')) {
fee.voidStatus = finished ? 'CONFIRMED' : 'PENDING_FINANCE'
if (failure === 'early-confirm') fee.sourceStatus = 'VOIDED'
data = fee
} else { fee = { sourceFeeId: '81', sourceVersion: 1, customerId: body.customerId, reviewStatus: 'DRAFT' }; data = fee }
return { http: 200, body: { code: '200', data: structuredClone(data) } }
} }
return { calls, get cleanup() { return cleanup }, options: { env, outputRoot, contextFactory: () => context,
wait: async () => {}, readers: { preflight: async () => {}, readFleetInventory: async scope => ({ scope, balances: [], positions: [], ledger: [] }) },
faultFactory: () => ({ prepare: async () => { if (['prepare', 'preflight'].includes(failure)) throw Error('PREPARE_FAILED') },
readProof: async () => { finished = true; Object.assign(fee, { sourceStatus: 'VOIDED', voidStatus: 'CONFIRMED' }); return { status: 'VERIFIED' } },
cleanup: async () => { cleanup++; return failure === 'preflight' || failure === 'missing-restoration'
? { attempted: false } : { restored: failure !== 'restore', healthy: true, proxyStopped: true } } }) } }
}
test('fee void plan has no network and refuses implicit environment or ambiguous customer scope', async () => {
assert.equal((await runFeeVoid(parseFeeVoidArguments([]), { env: {} })).execute, false)
for (const args of [['--execute'], ['--environment', 'PROD'], ['--run-id', '../escape'], ['--plan', '--execute']])
assert.throws(() => parseFeeVoidArguments(args))
await assert.rejects(runFeeVoid({ ...config(), controlCustomerId: '41' }, { env }), /distinct/)
await assert.rejects(runFeeVoid(config(), { env: { ...env, CHONGCHONG_ENV: 'prod' } }), /TEST/)
})
for (const failure of [undefined, 'prepare', 'preflight', 'early-confirm', 'isolation', 'pool', 'restore', 'missing-restoration', 'logout']) {
test(`fee void ${failure || 'success'} retains evidence and releases its lock after cleanup`, async () => {
const root = mkdtempSync(join(tmpdir(), 'fee-void-contract-'))
try {
const h = harness(root, failure), report = await runFeeVoid(config(), h.options)
assert.equal(report.success, !failure, JSON.stringify(report))
assert.equal(h.cleanup, 1)
if (failure === 'preflight') assert.equal(report.stage, 'FAILED')
if (failure === 'missing-restoration') assert.equal(report.stage, 'RESTORATION_FAILED')
assert.equal(h.calls.filter(c => c.path === '/api/auth/logout').length, 1)
assert.equal(existsSync(join(root, 'reports/.test-event-recovery.lock')), false)
const path = join(root, 'reports/fee-void-offline/report.json'), saved = readFileSync(path, 'utf8')
assert.doesNotMatch(saved, /private-password|private-access|private-refresh/)
if (failure === 'logout') assert.equal(JSON.parse(saved).logoutError, 'LOGOUT_TRANSPORT')
const previous = h.calls.length
assert.equal((await runFeeVoid(config(), h.options)).success, false)
assert.equal(h.calls.length, previous)
assert.equal(readFileSync(path, 'utf8'), saved)
} finally {
assert.equal(dirname(resolve(root)), resolve(tmpdir()))
assert.ok(basename(root).startsWith('fee-void-contract-'))
rmSync(root, { recursive: true, force: true })
}
})
}