121 lines
6.8 KiB
JavaScript
121 lines
6.8 KiB
JavaScript
'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;
|
||
});
|