170 lines
9.6 KiB
JavaScript
170 lines
9.6 KiB
JavaScript
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const vm = require('vm');
|
|
|
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'account-credentials-'));
|
|
const dbPath = path.join(tempDir, 'accounts.db');
|
|
const Config = require('../src/core/config');
|
|
Config.DATA_DIR = tempDir;
|
|
Config.DB_PATH = dbPath;
|
|
const database = require('../src/core/database');
|
|
|
|
const template = '{{账号}}----{{密码}}----{{辅助邮箱}}----{{2fa}}----{{接码链接}}----{{rt凭证}}';
|
|
const importedLine = 'user@example.com----OldPassword----无辅助邮箱----abcdef234567----https://sms.example/session----1//OLD_REFRESH_TOKEN';
|
|
|
|
(async () => {
|
|
try {
|
|
database.initDB();
|
|
const result = database.importAccountsFromText(importedLine, '----', '未登录', template);
|
|
assert.strictEqual(result.successCount, 1);
|
|
assert.strictEqual(result.errorCount, 0);
|
|
|
|
let account = database.getAccount('user@example.com');
|
|
assert.strictEqual(account.sms_url, 'https://sms.example/session', '导入模板应保存接码链接');
|
|
assert.strictEqual(account.ag_refresh_token, '1//OLD_REFRESH_TOKEN', '导入模板应保存 RT 凭证');
|
|
|
|
database.updateAccountPassword(account.email, 'NewPassword');
|
|
account = database.getAccount(account.email);
|
|
assert.strictEqual(account.sms_url, 'https://sms.example/session', '修改密码不应清除接码链接');
|
|
assert.strictEqual(account.ag_refresh_token, '1//OLD_REFRESH_TOKEN', '修改密码不应清除已导入的 RT 凭证');
|
|
|
|
database.updateAccountAgToken(account.email, '1//NEW_REFRESH_TOKEN');
|
|
account = database.getAccount(account.email);
|
|
assert.strictEqual(account.ag_refresh_token, '1//NEW_REFRESH_TOKEN', '新获取的 RT 凭证应覆盖导入值');
|
|
|
|
const appSource = fs.readFileSync(path.join(__dirname, '..', 'public', 'app.js'), 'utf8');
|
|
let clipboardText = '';
|
|
const context = vm.createContext({
|
|
console, Set, Map, Date, JSON, Math, String, Number, Object, Array, RegExp, Promise, Uint8Array,
|
|
setTimeout, clearTimeout,
|
|
navigator: { clipboard: { writeText(text) { clipboardText = text; } } },
|
|
});
|
|
vm.runInContext(`${appSource}\nthis.__geminiApp = geminiApp;`, context, { filename: 'public/app.js' });
|
|
|
|
const app = context.__geminiApp();
|
|
assert.strictEqual(app.deleteBrowserWindowsOnStage, false, '暂存时删除比特窗口必须默认关闭');
|
|
app.settings = {
|
|
import_templates: JSON.stringify([
|
|
{ name: '默认4字段(----)', pattern: '{{账号}}----{{密码}}----{{辅助邮箱}}----{{2fa}}' },
|
|
]),
|
|
};
|
|
app.saveSetting = async (key, value) => { app.settings[key] = value; };
|
|
await app.loadImportTemplates();
|
|
assert.strictEqual(
|
|
app.importTemplates[0].pattern,
|
|
'{{账号}}----{{密码}}----{{辅助邮箱}}----{{2fa}}----{{rt凭证}}',
|
|
'已有配置升级后应自动补入默认5字段模板',
|
|
);
|
|
assert.ok(app.importTemplates.some(item => item.pattern === template), '升级后仍应保留默认6字段模板');
|
|
|
|
app.importSelectedIndex = app.importTemplates.findIndex(item => item.pattern === template);
|
|
app.importText = importedLine;
|
|
const preview = app.getImportPreview()[0];
|
|
assert.deepStrictEqual(
|
|
Array.from(preview, col => col.label),
|
|
['账号', '密码', '辅助邮箱', '2fa', '接码链接', 'rt凭证'],
|
|
'导入预览应显示全部6个字段',
|
|
);
|
|
|
|
const shiftedLine = 'shifted@example.com----Password----JBSWY3DPEHPK3PXP----1//SHIFTED_REFRESH_TOKEN';
|
|
const shiftedResult = database.importAccountsFromText(shiftedLine, '----', '未登录', template);
|
|
assert.strictEqual(shiftedResult.successCount, 1);
|
|
const shifted = database.getAccount('shifted@example.com');
|
|
assert.strictEqual(shifted.secret_key, 'jbswy3dpehpk3pxp', '应自动纠正被放入辅助邮箱列的2FA密钥');
|
|
assert.strictEqual(shifted.ag_refresh_token, '1//SHIFTED_REFRESH_TOKEN', '应自动纠正被放入2FA列的RT凭证');
|
|
|
|
const noSmsLine = 'nosms@example.com----Password--------JBSWY3DPEHPK3PXP----1//NO_SMS_REFRESH_TOKEN';
|
|
const noSmsResult = database.importAccountsFromText(noSmsLine, '----', '未登录', template);
|
|
assert.strictEqual(noSmsResult.successCount, 1);
|
|
const noSms = database.getAccount('nosms@example.com');
|
|
assert.strictEqual(noSms.secret_key, 'jbswy3dpehpk3pxp');
|
|
assert.strictEqual(noSms.sms_url, null, 'RT凭证不能留在接码链接字段');
|
|
assert.strictEqual(noSms.ag_refresh_token, '1//NO_SMS_REFRESH_TOKEN');
|
|
|
|
const fourFieldTemplate = '{{账号}}----{{密码}}----{{辅助邮箱}}----{{2fa}}';
|
|
const swappedLine = 'swapped@example.com----Password----JBSWY3DPEHPK3PXP----recovery@example.com';
|
|
const swappedFourFieldResult = database.importAccountsFromText(swappedLine, '----', '未登录', fourFieldTemplate);
|
|
assert.strictEqual(swappedFourFieldResult.successCount, 1);
|
|
const swappedFourField = database.getAccount('swapped@example.com');
|
|
assert.strictEqual(swappedFourField.recovery_email, 'recovery@example.com', '第三列是2FA、第四列是辅助邮箱时应自动换位');
|
|
assert.strictEqual(swappedFourField.secret_key, 'jbswy3dpehpk3pxp');
|
|
|
|
const packedLine = 'packed@example.com----Password--------JBSWY3DPEHPK3PXP----1//PACKED_REFRESH_TOKEN';
|
|
const packedResult = database.importAccountsFromText(packedLine, '----', '未登录', fourFieldTemplate);
|
|
assert.strictEqual(packedResult.successCount, 1);
|
|
const packed = database.getAccount('packed@example.com');
|
|
assert.strictEqual(packed.recovery_email, null);
|
|
assert.strictEqual(packed.secret_key, 'jbswy3dpehpk3pxp', '4字段模板应从最后一列拆出2FA');
|
|
assert.strictEqual(packed.ag_refresh_token, '1//PACKED_REFRESH_TOKEN', '4字段模板应保留后续RT凭证');
|
|
|
|
app.importSelectedIndex = app.importTemplates.findIndex(item => item.pattern === fourFieldTemplate);
|
|
app.importText = swappedLine;
|
|
const swappedPreview = app.getImportPreview()[0];
|
|
assert.strictEqual(swappedPreview.find(col => col.label === '辅助邮箱').value, 'recovery@example.com');
|
|
assert.strictEqual(swappedPreview.find(col => col.label === '2fa').value, 'JBSWY3DPEHPK3PXP');
|
|
|
|
app.importText = packedLine;
|
|
const packedPreview = app.getImportPreview()[0];
|
|
assert.strictEqual(packedPreview.find(col => col.label === '2fa').value, 'JBSWY3DPEHPK3PXP');
|
|
assert.strictEqual(packedPreview.find(col => col.label === 'rt凭证').value, '1//PACKED_REFRESH_TOKEN');
|
|
|
|
const mixedSeparatorLine = 'mixed@example.com---Password---辅空---JBSW Y3DP EHPK 3PXP JBSW Y3DP EHPK 3PXP----1//MIXED_SEPARATOR_REFRESH_TOKEN';
|
|
const mixedSeparatorResult = database.importAccountsFromText(mixedSeparatorLine, '----', '未登录', fourFieldTemplate);
|
|
assert.strictEqual(mixedSeparatorResult.successCount, 1, '应兼容3个和4个横线混用的分隔符');
|
|
const mixedSeparator = database.getAccount('mixed@example.com');
|
|
assert.strictEqual(mixedSeparator.password, 'Password');
|
|
assert.strictEqual(mixedSeparator.recovery_email, '辅空');
|
|
assert.strictEqual(mixedSeparator.secret_key.replace(/\s/g, ''), 'jbswy3dpehpk3pxpjbswy3dpehpk3pxp');
|
|
assert.strictEqual(mixedSeparator.ag_refresh_token, '1//MIXED_SEPARATOR_REFRESH_TOKEN');
|
|
|
|
app.importText = mixedSeparatorLine;
|
|
const mixedSeparatorPreview = app.getImportPreview()[0];
|
|
assert.strictEqual(mixedSeparatorPreview.find(col => col.label === '账号').value, 'mixed@example.com');
|
|
assert.strictEqual(mixedSeparatorPreview.find(col => col.label === '密码').value, 'Password');
|
|
assert.strictEqual(mixedSeparatorPreview.find(col => col.label === '辅助邮箱').value, '辅空');
|
|
assert.ok(mixedSeparatorPreview.find(col => col.label === '2fa').value.includes('JBSW Y3DP'));
|
|
assert.strictEqual(mixedSeparatorPreview.find(col => col.label === 'rt凭证').value, '1//MIXED_SEPARATOR_REFRESH_TOKEN');
|
|
|
|
app._exportList = [{ ...account, password: 'NewPassword', recovery_email: '无辅助邮箱', secret_key: 'abcdef234567' }];
|
|
Object.keys(app.exportFields).forEach(key => { app.exportFields[key] = false; });
|
|
['email', 'password', 'recovery_email', 'secret_key', 'sms_url', 'ag_refresh_token'].forEach(key => {
|
|
app.exportFields[key] = true;
|
|
});
|
|
app.exportMode = { file: false, clipboard: true };
|
|
app.addLog = () => {};
|
|
await app.confirmExport();
|
|
assert.strictEqual(
|
|
clipboardText,
|
|
'user@example.com----NewPassword----无辅助邮箱----abcdef234567----https://sms.example/session----1//NEW_REFRESH_TOKEN',
|
|
'导出应按6字段顺序使用最新密码、原接码链接和最新 RT 凭证',
|
|
);
|
|
|
|
let stagingPayload = null;
|
|
context.fetch = async (url, options = {}) => {
|
|
if (url === '/api/accounts/staging') {
|
|
stagingPayload = JSON.parse(options.body);
|
|
return { json: async () => ({ success: true, count: 1, batchId: 1, browserDeleteRequested: true, browsersFound: 0, browsersDeleted: 0, browserDeleteFailed: 0 }) };
|
|
}
|
|
if (url === '/api/accounts') {
|
|
return { json: async () => ({ success: true, data: [] }) };
|
|
}
|
|
throw new Error(`未处理的测试请求: ${url}`);
|
|
};
|
|
app.deleteBrowserWindowsOnStage = true;
|
|
app.exportStagingNote = '测试文件夹';
|
|
app.selectedEmails = new Set([account.email]);
|
|
await app.stageExportAccounts([account], ['email', 'password']);
|
|
assert.strictEqual(stagingPayload.deleteBrowserWindows, true, '用户打开开关后应请求删除比特窗口');
|
|
|
|
console.log('account_credentials_import_export.test.js: PASS');
|
|
} finally {
|
|
try { database.getDb().close(); } catch (_) {}
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
})().catch(error => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|