'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(), 'refund-deadline-')); const Config = require('../src/core/config'); Config.DATA_DIR = tempDir; Config.DB_PATH = path.join(tempDir, 'accounts.db'); const database = require('../src/core/database'); const { runRefundDeadlineCheck, deliverRefundDeadlineReport, _private, } = require('../src/services/refund_deadline_service'); const deadlineSchedulerPrivate = require('../src/services/refund_deadline_scheduler')._private; (async () => { try { database.initDB(); const columns = database.getDb().pragma('table_info(accounts)').map(row => row.name); assert(columns.includes('refund_deadline_at')); const now = new Date('2026-08-26T08:00:00.000Z'); const accounts = ['due@example.com', 'future@example.com', 'refunded@example.com', 'started@example.com']; for (const email of accounts) { database.upsertAccount({ email, password: `p-${email}`, recovery_email: `r-${email}`, secret_key: 'secret', status: '已充值提取' }); database.updateAccountChatgptResult(email, '已登录', JSON.stringify({ accessToken: `session-${email}` })); } database.updateAccountRefundDeadline('due@example.com', '2026-08-26T07:59:00.000Z'); database.updateAccountRefundDeadline('future@example.com', '2026-08-27T08:00:00.000Z'); database.updateAccountRefundDeadline('refunded@example.com', '2026-08-26T07:59:00.000Z'); database.updateAccountRefundDeadline('started@example.com', '2026-08-26T07:59:00.000Z'); database.updateAccountRefundStatus('refunded@example.com', '已退款'); database.updateAccountRefundProcessStatus('started@example.com', '已发起'); assert.deepStrictEqual(database.getDueRefundDeadlineAccounts(now).map(row => row.email), ['due@example.com']); const calls = []; let syncedRefundEntries = null; const result = await runRefundDeadlineCheck({ database, now, loadRefundCredentials: async () => ({ accessToken: 'owner-token', proxies: ['socks5://proxy:1080'] }), executeRefund: async payload => { calls.push(payload.expectedEmail); return { success: true, cleanupOk: true }; }, syncRefundStatuses: async payload => { syncedRefundEntries = payload.entries.map(item => ({ email: item.email, status: item.refundProcessStatus, result: item.refundResult, })); return { success: true, updatedCount: payload.entries.length, skippedCount: 0 }; }, delay: async () => {}, }); assert.strictEqual(result.dueCount, 1); assert.strictEqual(result.refundAttemptedCount, 1); assert.strictEqual(result.refundSuccessCount, 1); assert.deepStrictEqual(calls, ['due@example.com']); assert.deepStrictEqual(syncedRefundEntries, [ { email: 'due@example.com', status: '已发起', result: '成功' }, ], '截止时间退款结束后应只回写本轮已处理账号的退款流程状态'); assert.strictEqual(database.getAccount('due@example.com').refund_process_status, '已发起'); assert.strictEqual(database.getAccount('due@example.com').account_refund_status, '无状态'); assert.match(result.text, /到期 1|提交 1|成功 1/); assert.strictEqual(result.files.length, 1); const csv = result.files[0].content.toString('utf8'); assert(csv.includes('"未查询(截止时间触发)"')); assert(csv.includes('"due@example.com"')); let delivered = null; await deliverRefundDeadlineReport(result, { database, sendReport: async payload => { delivered = payload; return { success: true }; }, }); assert(delivered); assert.strictEqual(delivered.files.length, 1); assert.strictEqual(_private.resultSignature(result.results), result.signature); database.updateAccountsRefundDeadline(['future@example.com'], null); assert.strictEqual(database.getAccount('future@example.com').refund_deadline_at, null); for (const email of ['offset-a@example.com', 'offset-b@example.com', 'offset-missing@example.com']) { database.upsertAccount({ email, status: '已充值提取' }); } database.getDb().prepare('UPDATE accounts SET recharged_at = ? WHERE email = ?') .run('2026-08-26 01:00:00', 'offset-a@example.com'); database.getDb().prepare('UPDATE accounts SET recharged_at = ? WHERE email = ?') .run('2026-08-26 03:30:00', 'offset-b@example.com'); const offsetResult = database.updateAccountsRefundDeadlineFromRecharge([ 'offset-a@example.com', 'offset-b@example.com', 'offset-missing@example.com', ], 90 * 60 * 1000); assert.strictEqual(offsetResult.count, 2); assert.strictEqual(offsetResult.skippedCount, 1); assert.strictEqual(database.getAccount('offset-a@example.com').refund_deadline_at, '2026-08-26T02:30:00.000Z'); assert.strictEqual(database.getAccount('offset-b@example.com').refund_deadline_at, '2026-08-26T05:00:00.000Z'); assert.strictEqual(database.getAccount('offset-missing@example.com').refund_deadline_at, null); const intervalDb = { getSetting(key, fallback) { return key === 'refund_deadline_check_interval_seconds' ? '45' : fallback; }, }; assert.strictEqual(deadlineSchedulerPrivate.readIntervalSeconds(intervalDb), 45); assert.strictEqual(deadlineSchedulerPrivate.readIntervalSeconds({ getSetting: () => '1' }), 5); assert.strictEqual(deadlineSchedulerPrivate.readIntervalSeconds({ getSetting: () => '99999' }), 3600); const root = path.join(__dirname, '..'); const html = fs.readFileSync(path.join(root, 'public', 'index.html'), 'utf8'); const app = fs.readFileSync(path.join(root, 'public', 'app.js'), 'utf8'); const server = fs.readFileSync(path.join(root, 'server.js'), 'utf8'); const accountRoutes = fs.readFileSync(path.join(root, 'src', 'routes', 'accounts.js'), 'utf8'); assert.match(html, /退款截止时间/); assert.match(html, /截止检查间隔(秒)/); assert.match(html, /额度达到阈值或截止时间到达/); assert.match(html, /按每个账号的充值时间计算/); assert.match(html, /\+30分钟/); assert.match(html, /\+1天/); assert.match(html, /settings-select refund-deadline-unit-select/); assert.match(app, /\/api\/accounts\/batch\/refund-deadline/); assert.match(app, /mode:\s*'recharge_offset'/); assert.match(accountRoutes, /updateAccountsRefundDeadlineFromRecharge/); assert.match(server, /refund_deadline_scheduler/); console.log('refund_deadline.test.js: PASS'); } finally { try { database.getDb().close(); } catch (_) {} fs.rmSync(tempDir, { recursive: true, force: true }); } })().catch(error => { console.error(error); process.exitCode = 1; });