'use strict'; const assert = require('assert'); const fs = require('fs'); const os = require('os'); const path = require('path'); const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'recharge-db-')); const Config = require('../src/core/config'); Config.DATA_DIR = tempDir; Config.DB_PATH = path.join(tempDir, 'accounts.db'); const database = require('../src/core/database'); try { database.initDB(); const columns = database.getDb().pragma('table_info(accounts)').map(row => row.name); for (const column of [ 'recharge_status', 'recharge_task_id', 'recharge_message', 'recharged_at', 'recharge_operation_id', 'recharge_card_key', ]) { assert(columns.includes(column), `账号表应包含 ${column}`); } const attemptColumns = database.getDb().pragma('table_info(recharge_attempts)').map(row => row.name); for (const column of ['operation_id', 'email', 'attempt_no', 'remote_task_id', 'status']) { assert(attemptColumns.includes(column), `充值尝试表应包含 ${column}`); } const email = 'recharge@example.com'; database.upsertAccount({ email, password: 'secret' }); assert.strictEqual(database.updateAccountRecharge(email, '充值中', 123, null), true); let account = database.getAccount(email); assert.strictEqual(account.recharge_status, '充值中'); assert.strictEqual(account.recharge_task_id, '123'); assert.strictEqual(account.recharge_message, null); assert.strictEqual(account.message, null); assert.strictEqual(account.recharged_at, null); assert.strictEqual(database.updateAccountRecharge(email, '充值失败', 123, 'remote_error'), true); account = database.getAccount(email); assert.strictEqual(account.recharge_status, '充值失败'); assert.strictEqual(account.recharge_message, 'remote_error'); assert.strictEqual(account.message, 'remote_error', '充值结论应同步到通用处理结论'); assert.ok(account.recharged_at, '充值失败也应记录本次自动处理完成时间'); assert.strictEqual(database.updateAccountRecharge(email, '充值成功', 123, 'success'), true); account = database.getAccount(email); assert.strictEqual(account.recharge_status, '充值成功'); assert.strictEqual(account.recharge_task_id, '123'); assert.strictEqual(account.recharge_message, 'success'); assert.strictEqual(account.message, 'success', '充值成功后处理结论不应停在等待充值'); assert.ok(account.recharged_at, '充值成功应记录完成时间'); assert.strictEqual(database.updateAccountRecharge('missing@example.com', '充值成功'), false); const firstOperation = `cardpay-v1-${'a'.repeat(64)}`; const otherOperation = `cardpay-v1-${'b'.repeat(64)}`; assert.deepStrictEqual( database.claimAccountsForRecharge([{ email, operationId: firstOperation, cardKey: 'CARD-FIRST' }]), { success: true }, ); account = database.getAccount(email); assert.strictEqual(account.recharge_status, '充值中'); assert.strictEqual(account.recharge_operation_id, firstOperation); assert.strictEqual(account.recharge_task_id, null, '新操作占用时应清除上一任务编号'); assert.strictEqual(account.recharge_card_key, 'CARD-FIRST', '应保存本次实际使用的卡密'); assert.strictEqual(account.message, '等待充值', '进入充值队列时处理结论应显示等待充值'); database.updateAccountRecharge(email, '充值中', 456, '结果待确认'); assert.deepStrictEqual( database.claimAccountsForRecharge([{ email, operationId: firstOperation, cardKey: 'CARD-FIRST' }]), { success: true }, '相同操作必须允许幂等恢复', ); assert.strictEqual(database.getAccount(email).recharge_task_id, '456', '幂等恢复必须保留任务编号'); assert.deepStrictEqual( database.claimAccountsForRecharge([{ email, operationId: otherOperation, cardKey: 'CARD-OTHER' }]), { success: false, code: 'RECHARGE_ACCOUNT_BUSY' }, '未知结果时必须拒绝换卡并行充值', ); database.updateAccountRecharge(email, '等待已确认', 456, '等待人工确认'); assert.ok(database.getAccount(email).recharged_at, '等待人工确认应记录自动处理结束时间'); assert.deepStrictEqual( database.claimAccountsForRecharge([{ email, operationId: otherOperation, cardKey: 'CARD-OTHER' }]), { success: true }, '等待人工确认不是锁定状态,应允许人工决定后再次充值', ); account = database.getAccount(email); assert.strictEqual(account.recharge_status, '充值中'); assert.strictEqual(account.recharge_operation_id, otherOperation); assert.strictEqual(account.recharge_task_id, null, '再次充值应开始新的操作并清除旧任务编号'); assert.strictEqual(account.recharge_card_key, 'CARD-OTHER', '再次充值应覆盖为本次使用的卡密'); assert.strictEqual(account.recharged_at, null, '再次充值开始时应清空上一次完成时间'); const failedEmail = 'recharge-failed@example.com'; database.upsertAccount({ email: failedEmail, password: 'secret' }); assert.deepStrictEqual( database.claimAccountsForRecharge([{ email: failedEmail, operationId: firstOperation, cardKey: 'FAILED-CARD-FIRST', }]), { success: true }, ); database.updateAccountRecharge(failedEmail, '充值失败', null, '第一次充值失败'); assert.deepStrictEqual( database.claimAccountsForRecharge([{ email: failedEmail, operationId: otherOperation, cardKey: 'FAILED-CARD-RETRY', }]), { success: true }, '充值失败状态必须允许换卡再次充值', ); assert.strictEqual( database.getAccount(failedEmail).recharge_card_key, 'FAILED-CARD-RETRY', '失败后再次充值应记录新一轮使用的卡密', ); const guardedEmail = 'recharge-guarded@example.com'; database.upsertAccount({ email: guardedEmail, password: 'secret' }); assert.deepStrictEqual(database.claimAccountsForRecharge([{ email: guardedEmail, operationId: firstOperation, cardKey: 'GUARDED-CARD', }]), { success: true }); assert.strictEqual( database.recordRechargeAttempt(guardedEmail, firstOperation, 1, '701', 'pending', 'pending'), true, ); assert.strictEqual( database.recordRechargeAttempt(guardedEmail, firstOperation, 2, '702', 'failed', 'failed'), true, ); assert.strictEqual( database.recordRechargeAttempt(guardedEmail, firstOperation, 3, '703', 'success', 'success'), true, ); assert.deepStrictEqual( database.getRechargeAttempts(guardedEmail, firstOperation).map(row => [row.attempt_no, row.remote_task_id, row.status]), [[1, '701', 'pending'], [2, '702', 'failed'], [3, '703', 'success']], '同一次充值产生的全部远端任务号都必须保留', ); assert.strictEqual( database.updateAccountRechargeForOperation(guardedEmail, firstOperation, '充值成功', '703', 'success'), true, ); assert.strictEqual( database.updateAccountRechargeForOperation(guardedEmail, firstOperation, '充值失败', '702', 'late failure'), true, '晚到失败消息应被安全忽略', ); assert.strictEqual(database.getAccount(guardedEmail).recharge_status, '充值成功', '同一操作一旦成功不得被晚到失败状态降级'); assert.strictEqual( database.updateAccountRechargeForOperation(guardedEmail, otherOperation, '充值失败', '999', 'stale'), false, '旧操作或伪造操作号不得覆盖当前充值状态', ); console.log('recharge_database.test.js: PASS'); } finally { try { database.getDb().close(); } catch (_) {} fs.rmSync(tempDir, { recursive: true, force: true }); }