import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import test from 'node:test' const apiSource = readFileSync(new URL('../api/account.ts', import.meta.url), 'utf8') const viewSource = readFileSync(new URL('./FinanceAccountView.vue', import.meta.url), 'utf8') const routesSource = readFileSync(new URL('../routes.ts', import.meta.url), 'utf8') const permissionsSource = readFileSync(new URL('../../tenant-management/contracts.ts', import.meta.url), 'utf8') test('uses only authenticated client finance account endpoints without customer scope', () => { for (const path of ['/client/finance/v1/account', '/client/finance/v1/account/ledger', '/client/finance/v1/recharge-requests', '/client/finance/v1/recharge-channels']) { assert.match(apiSource, new RegExp(path.replaceAll('/', '\\/'))) } assert.match(apiSource, /headers: \{ 'Idempotency-Key': idempotencyKey \}/) assert.match(apiSource, /formData\.append\('amount', amount\)/) assert.match(apiSource, /formData\.append\('paymentMethod', paymentMethod\)/) assert.match(apiSource, /formData\.append\('file', file, file\.name\)/) assert.doesNotMatch(apiSource, /customerId|tenantId|mock|fallback/i) }) test('activates the account route and maps read and recharge permissions independently', () => { assert.match(routesSource, /path: 'finance\/account'[\s\S]*FinanceAccountView\.vue[\s\S]*release: 'active'[\s\S]*permission: 'merchant:finance:account:view'/) assert.match(permissionsSource, /'merchant:finance:account:view': \['merchant:finance:account:view'\]/) assert.match(permissionsSource, /'merchant:finance:recharge:create': \['merchant:finance:recharge:create'\]/) assert.match(viewSource, /auth\.hasPermission\('merchant:finance:recharge:create'\)/) assert.match(viewSource, / { for (const field of ['actualBalance', 'frozenBalance', 'availableBalance', 'creditLimit', 'creditUsed', 'availableCredit']) assert.match(viewSource, new RegExp(`account\\?\\.${field}`)) for (const field of ['creditUsedAfter', 'availableCreditAfter']) assert.match(viewSource, new RegExp(`row\\.${field}`)) assert.match(viewSource, /实际可用资金与可用授信合计/) assert.match(viewSource, /CREDIT_LIMIT: '授信调整'/) assert.match(viewSource, /row-key="businessNo"/) assert.match(viewSource, /row-key="requestNo"/) assert.match(viewSource, /empty-text="暂无资金流水"/) assert.match(viewSource, /empty-text="暂无充值申请"/) assert.match(viewSource, /account-state--error/) assert.match(viewSource, /v-loading="pageLoading"/) assert.match(viewSource, /function formatAccountAmount[\s\S]*value == null \? '—' : formatAmount\(value\)/) assert.match(viewSource, /formatAccountAmount\(account\?\.actualBalance\)/) assert.doesNotMatch(viewSource, /requestId|ledgerId|customerId|tenantId/) }) test('matches the frozen recharge workflow and preserves an idempotency key for failed retries', () => { assert.match(viewSource, /title="账户充值" width="760px"/) for (const amount of ['50.00', '100.00', '200.00', '500.00']) assert.match(viewSource, new RegExp(amount.replace('.', '\\.'))) assert.match(viewSource, /\['WECHAT', 'ALIPAY'\]/) assert.match(viewSource, /selectedChannel\.value\?\.enabled && selectedChannel\.value\.qrCodeUrl/) assert.match(viewSource, /accept="\.jpg,\.jpeg,\.png,image\/jpeg,image\/png"/) assert.match(viewSource, /if \(!pendingIdempotencyKey\)[\s\S]*globalThis\.crypto\?\.randomUUID\?\.\(\)/) assert.match(viewSource, /pendingIdempotencyKey = ''[\s\S]*rechargeVisible\.value = false/) assert.match(viewSource, /:disabled="!canSubmit"/) assert.match(viewSource, /await loadAccountData\(\)/) assert.doesNotMatch(viewSource, /payment SDK|支付成功|模拟成功/) })