167 lines
7.5 KiB
JavaScript
167 lines
7.5 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(), '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 });
|
|
}
|