Files
go_bt/tests/google_account_ag_feature.test.js
T

117 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { createGoogleAccountCredentialHandlers } = require('../src/routes/google_account_credentials');
function responseStub() {
return {
headers: {}, statusCode: 200, payload: null,
set(name, value) { this.headers[String(name).toLowerCase()] = String(value); return this; },
status(code) { this.statusCode = code; return this; },
json(payload) { this.payload = payload; return this; },
};
}
(async () => {
const root = path.join(__dirname, '..');
const html = fs.readFileSync(path.join(root, 'public', 'index.html'), 'utf8');
const appSource = fs.readFileSync(path.join(root, 'public', 'app.js'), 'utf8');
const server = fs.readFileSync(path.join(root, 'server.js'), 'utf8');
const operations = fs.readFileSync(path.join(root, 'src', 'routes', 'operations.js'), 'utf8');
const worker = fs.readFileSync(path.join(root, 'src', 'workers', 'task_runner.js'), 'utf8');
const sse = fs.readFileSync(path.join(root, 'src', 'core', 'sse.js'), 'utf8');
assert.match(html, /runAgGetCredential\(\)[\s\S]{0,300}runGeminiCliGetCredential\(\)[\s\S]{0,300}openGoogleAccountAgModal\(\)/,
'获取Cli凭证按钮必须位于获取凭证和链接提取按钮之间');
assert.match(html, /Google 账号取号 API[\s\S]{0,2500}googleAccountCredentialForm\.value/,
'设置页应提供加密取号 API 配置');
assert.match(html, /处理账号数量(1~5000)[\s\S]{0,800}并发参数(兼容保留)/,
'任务弹窗应说明并发参数只为兼容保留');
assert.match(html, /临时比特浏览器[\s\S]{0,120}严格按邮箱串行处理:完成一个、删除一个,再开始下一个/,
'任务弹窗应告知用户临时比特浏览器严格串行');
assert(server.includes("/api/google-account-credentials"), '服务端应挂载取号凭据路由');
assert(operations.includes("'google_account_ag_credential'"), '取号任务必须受本机凭据任务保护');
assert(operations.includes('importAccountsFromText'), 'Worker 应通过主线程安全导入新账号');
assert(operations.includes("msg.type === 'task_accounts'"), '导入后应实时通知前端新任务账号');
assert(sse.includes('state.taskAccounts'), '晚连接或重连时也必须补发本次新增账号范围');
assert(worker.includes("case 'google_account_ag_credential':"), 'Worker 应实现链接取号任务');
assert(worker.includes("map(account => `${account.email}----${account.password}----${account.recovery_email || ''}`)"),
'供应数据应按邮箱、密码、辅助邮件格式导入');
assert(worker.includes("const account = { ...normalizeAccount(accountRow), proxy_ip: '' };"),
'新建浏览器前必须强制清空代理');
assert(worker.includes("const defaultCustomerName = '企业字-Free';"),
'链接取号导入账号必须使用默认客户名“企业字-Free”');
assert.match(worker, /fetchGoogleAccounts\(\{[\s\S]{0,220}quantity:\s*1/,
'每次供应接口调用只能提取 1 个账号');
assert(worker.includes('const taskConcurrency = 1;'),
'浏览器账号必须固定串行,删除当前临时窗口后再处理下一个');
assert(worker.includes('const taskResults = await runConcurrent(taskSlots'),
'账号应通过滑动队列按顺序处理');
const fetchOneAt = worker.indexOf('quantity: 1,');
const createBrowserAt = worker.indexOf('taskBrowserLifecycle.run(account', fetchOneAt);
const acquireCredentialAt = worker.indexOf("taskType: 'ag_get_credential'", createBrowserAt);
assert(fetchOneAt >= 0 && createBrowserAt > fetchOneAt && acquireCredentialAt > createBrowserAt,
'每个工位必须按“取 1 个→准备临时比特窗口→获取反重力凭证”的顺序处理');
const slotTaskEnd = worker.indexOf('}, taskConcurrency', createBrowserAt);
assert.match(worker.slice(createBrowserAt, slotTaskEnd), /forceDirect:\s*true/,
'取号任务的临时比特窗口必须明确使用直连模式');
assert(worker.includes('ATOM_STEPS.ag_get_credential.step'),
'取号后必须复用反重力获取凭证步骤');
assert(!worker.includes('supplied.raw'), 'Worker 不得记录供应商原始响应');
let saved = null;
let readCalls = 0;
const handlers = createGoogleAccountCredentialHandlers({
async getGoogleAccountCredentialMeta() {
readCalls += 1;
return { configured: true, keyMasked: 'test••••key', timelive: 60, updatedAt: null };
},
async saveGoogleAccountCredentials(value, timelive) {
saved = { value, timelive };
return { configured: true, keyMasked: 'test••••key', timelive, updatedAt: null };
},
async deleteGoogleAccountCredentials() {
return { configured: false, keyMasked: '', timelive: 60, updatedAt: null };
},
});
const localPut = responseStub();
await handlers.put({ socket: { remoteAddress: '127.0.0.1' }, body: { value: 'full-secret-key', timelive: 90 } }, localPut);
assert.strictEqual(localPut.statusCode, 200);
assert.deepStrictEqual(saved, { value: 'full-secret-key', timelive: 90 });
assert(!JSON.stringify(localPut.payload).includes('full-secret-key'), '凭据响应不得返回完整 Key');
assert.strictEqual(localPut.headers['cache-control'], 'no-store');
const remoteGet = responseStub();
await handlers.get({ socket: { remoteAddress: '192.0.2.20' } }, remoteGet);
assert.strictEqual(remoteGet.statusCode, 403);
assert.strictEqual(readCalls, 0, '远程请求不得读取本机取号凭据');
const context = vm.createContext({
console, Set, Map, Date, JSON, Math, String, Number, Object, Array, RegExp, Promise,
setTimeout, clearTimeout,
});
vm.runInContext(`${appSource}\nthis.__geminiApp = geminiApp;`, context, { filename: 'public/app.js' });
const app = context.__geminiApp();
app.googleAccountCredentialMeta.configured = true;
app.loadGoogleAccountCredentialMeta = async () => {};
await app.openGoogleAccountAgModal();
assert.strictEqual(app.showGoogleAccountAgModal, true);
let launch = null;
app.runTask = async (taskType, params, accounts) => { launch = { taskType, params, accounts }; return true; };
app.googleAccountAgQuantity = 12;
app.googleAccountAgConcurrency = 4;
await app.confirmGoogleAccountAgTask();
assert.strictEqual(launch.taskType, 'google_account_ag_credential');
assert.strictEqual(launch.params.quantity, 12);
assert.strictEqual(launch.params.threadCount, 4);
assert.strictEqual(Array.from(launch.accounts).length, 0);
console.log('google_account_ag_feature.test.js: PASS');
})().catch(error => {
console.error(error);
process.exitCode = 1;
});