'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; });