Files
go_bt/tests/google_account_ag_feature.test.js

121 lines
6.8 KiB
JavaScript
Raw Permalink 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}并发数量(1~30)/,
'任务弹窗应允许用户设置实际并发数量');
assert.match(html, /临时比特浏览器[\s\S]{0,180}每个并发工位完成一个账号并删除对应浏览器后,再领取下一个账号/,
'任务弹窗应说明每个并发工位独立执行完整浏览器生命周期');
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 browserTaskConcurrency = threadCount;'),
'浏览器任务必须采用页面传入的并发数');
assert(worker.includes('const taskConcurrency = browserTaskConcurrency;'),
'链接取号任务必须采用页面传入的并发数');
assert(!worker.includes('已将浏览器任务并发'),
'浏览器任务不得静默把用户设置的并发数改成 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;
});