Files
go_bt/tests/refund_deadline.test.js

138 lines
6.7 KiB
JavaScript

'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;
});