import test from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' import { EXPORT_SPECS, REQUIRED_PERMISSIONS, run } from './finance-downloads.mjs' import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs' const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'finance-downloads.mjs'), 'utf8') test('八类 Finance XLSX 契约均有正式表头并在完整链末尾执行', () => { assert.deepEqual(REQUIRED_PERMISSIONS, [ 'platform:download:list', 'platform:finance:customer-account:export', 'platform:finance:customer-arap:export', 'platform:finance:customer-statement:export', 'platform:finance:settlement-ar:export', 'platform:finance:settlement-ap:export', ]) assert.deepEqual(Object.keys(EXPORT_SPECS), [ 'FINANCE_CUSTOMER_ACCOUNT_LIST', 'FINANCE_CUSTOMER_ACCOUNT_LEDGER', 'FINANCE_FEE_SOURCE_LIST', 'FINANCE_RECONCILIATION_STATEMENT_LIST', 'FINANCE_RECEIVABLE_SETTLEMENT_LIST', 'FINANCE_RECEIVABLE_SETTLEMENT_DETAIL', 'FINANCE_PAYABLE_SETTLEMENT_LIST', 'FINANCE_PAYABLE_SETTLEMENT_DETAIL', ]) assert.ok(Object.values(EXPORT_SPECS).every((headers) => headers.length >= 9)) const order = normalizeModuleOrder(['finance-downloads', 'finance-settlement-full']) assert.deepEqual(order.slice(-5), [ 'finance-full-fixtures', 'finance-reconciliation-full', 'finance-settlement-full', 'finance-direct-paid-isolation', 'finance-downloads', ]) }) test('验收只使用本轮 marker fixture,解析真实 XLSX 并覆盖门禁和失败信息', () => { assert.match(source, /fixture\.marker !== context\.marker/) assert.match(source, /parseXlsx\(download\.bytes\)/) assert.match(source, /processedRows/) assert.match(source, /60 秒同参请求合并/) assert.match(source, /同用户并发导出门禁/) assert.match(source, /无 Finance export 权限拒绝创建/) assert.match(source, /Provider 失败信息可见/) assert.match(source, /9223372036854775806/) assert.match(source, /find\(\(item\) => item\?\.direction === 'RECEIVABLE'\)/) assert.doesNotMatch(source, /exportType:\s*['"]CUSTOMER_LIST/) assert.doesNotMatch(source, /FINANCE_FEE_SOURCE_LIST:[^\n]*参与结算/) }) test('未部署时能力探测后记录部署前置,不借历史业务数据', async () => { const steps = []; const gaps = [] const context = { marker: 'ADMIN-API-PROBE', createdResources: {}, apiSuccess(module, name, response) { const passed = response.http === 200 && String(response.body?.code) === '200' steps.push({ module, name, passed }); return passed ? response.body.data : null }, assert(module, name) { steps.push({ module, name, passed: false }) }, addStep(module, name, response, passed, evidence, options) { steps.push({ module, name, passed, evidence, options }) }, addCoverageGap(...args) { gaps.push(args) }, async request(method, path) { if (path.endsWith('/authorization/permissions')) return { http: 200, body: { code: '200', data: REQUIRED_PERMISSIONS } } return { http: 200, body: { code: 'A0403', message: 'not deployed' } } }, } await run(context) assert.equal(context.createdResources.financeExportTasks.marker, context.marker) assert.equal(gaps.length, 1) assert.match(gaps[0][3], /部署后重跑完整链/) assert.equal(steps.some((step) => step.name === '读取客户账户筛选事实'), false) }) test('同参导出只合并进行中任务,已完成任务允许按同参重新导出', () => { assert.match(source, /60 秒同参请求合并/) assert.match(source, /String\(originalStatus\) === 'SUCCEEDED'/) assert.match(source, /File 只合并 PENDING\/RUNNING 的同参任务/) })