'use strict'; const assert = require('assert'); const fs = require('fs'); const path = require('path'); const vm = require('vm'); (async () => { const root = path.join(__dirname, '..'); const oauthPath = require.resolve('../src/services/openai_oauth_service'); const stepPath = require.resolve('../src/pipeline/steps/openai_query_plan'); const originalOauthCache = require.cache[oauthPath]; const originalStepCache = require.cache[stepPath]; const enrichedAuth = { access_token: 'access-new', refresh_token: 'refresh-old', plan_type: 'plus', subscription_expires_at: '2027-02-01T00:00:00.000Z', usage: { rate_limit: { primary_window: { used_percent: 37.5 } } }, }; const queryCalls = []; require.cache[oauthPath] = { id: oauthPath, filename: oauthPath, loaded: true, exports: { queryAndEnrichOpenAIAuthJsonDirect: async (_authJson, proxyIp, _log, options) => { queryCalls.push({ authJson: _authJson, proxyIp, options }); return { success: true, authJson: { ...enrichedAuth, tested_proxy: proxyIp, tested_mode: options.connectionMode }, sessionText: options.sessionText, details: { errors: [] }, error: null, }; }, }, }; delete require.cache[stepPath]; const queryPlanStep = require(stepPath); const writes = []; const ctx = { account: { email: 'plan@example.com' }, page: {}, params: { connectionMode: 'direct' }, log: () => {}, results: {}, dbWrite: async (fn, args) => { writes.push({ fn, args }); if (fn === 'getAccount') { return { email: 'plan@example.com', proxy_ip: 'proxy.example.com:1080:user:pass', openai_auth_json: JSON.stringify({ access_token: 'old' }), }; } return true; }, }; await queryPlanStep(ctx); const saved = writes.find(call => call.fn === 'updateAccountOpenAIPlan'); assert(saved, '独立套餐任务应保存套餐结果'); assert.deepStrictEqual(saved.args.slice(0, 5), [ 'plan@example.com', '查询成功', 'plus', 37.5, '2027-02-01T00:00:00.000Z', ]); assert.strictEqual(JSON.parse(saved.args[5]).access_token, 'access-new'); assert.strictEqual(JSON.parse(saved.args[5]).tested_proxy, 'proxy.example.com:1080:user:pass'); assert.strictEqual(JSON.parse(saved.args[5]).tested_mode, 'direct'); assert.strictEqual(ctx.results.openaiPlan.success, true); const sessionOnlyWrites = []; const sessionOnlyCtx = { ...ctx, account: { email: 'session-only@gmail.com' }, results: {}, dbWrite: async (fn, args) => { sessionOnlyWrites.push({ fn, args }); if (fn === 'getAccount') { return { email: 'session-only@gmail.com', proxy_ip: '', openai_auth_json: null, chatgpt_session: JSON.stringify({ accessToken: 'session-access', sessionToken: 'session-token' }), }; } return true; }, }; await queryPlanStep(sessionOnlyCtx); assert.strictEqual(sessionOnlyCtx.results.openaiPlan.success, true); assert.strictEqual(queryCalls.at(-1).options.expectedEmail, 'session-only@gmail.com'); assert.match(queryCalls.at(-1).options.sessionText, /session-token/); assert(sessionOnlyWrites.some(call => call.fn === 'updateAccountOpenAIPlan'), '没有授权 JSON、但有 Session 时不得跳过套餐查询'); const { openAIPlanFailureStatus } = queryPlanStep._private; assert.strictEqual( openAIPlanFailureStatus({ code: 'OPENAI_ACCOUNT_QUERY_UNAUTHORIZED', message: 'Request failed' }), 'Token失效(401)', ); assert.strictEqual( openAIPlanFailureStatus({ code: 'OPENAI_PLAN_PROXY_AUTH_FAILED', message: 'User was rejected' }), '代理认证失败', ); assert.strictEqual( openAIPlanFailureStatus({ code: 'OPENAI_PLAN_PROXY_CONNECTION_FAILED', message: 'connect timeout' }), '代理连接失败', ); assert.strictEqual( openAIPlanFailureStatus({ code: 'OPENAI_PLAN_DIRECT_CONNECTION_FAILED', message: 'connect timeout' }), '直连失败', ); assert.strictEqual( openAIPlanFailureStatus({ code: 'UNEXPECTED', message: 'unexpected response body' }), '套餐查询异常:unexpected response body', ); if (originalOauthCache) require.cache[oauthPath] = originalOauthCache; else delete require.cache[oauthPath]; if (originalStepCache) require.cache[stepPath] = originalStepCache; else delete require.cache[stepPath]; const recipesPath = require.resolve('../src/pipeline/recipes'); delete require.cache[recipesPath]; const { RECIPES } = require(recipesPath); assert.strictEqual(RECIPES.openai_get_auth.length, 1); assert.strictEqual(RECIPES.openai_query_plan.length, 1); assert.notStrictEqual( RECIPES.openai_get_auth[0], RECIPES.openai_query_plan[0], '提取授权JSON和查询套餐必须是两个独立步骤', ); const appSource = fs.readFileSync(path.join(root, 'public', 'app.js'), 'utf8'); 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(); let launchedTask = ''; let launchedParams = null; app.runTask = (taskType, params) => { launchedTask = taskType; launchedParams = params; }; app.runOpenAIQueryPlan(); assert.strictEqual(app.showOpenAIPlanRunModal, true); assert.strictEqual(app.openAIPlanConnectionMode, 'browser_proxy'); app.openAIPlanConnectionMode = 'direct'; app.confirmOpenAIQueryPlan(); assert.strictEqual(launchedTask, 'openai_query_plan'); assert.strictEqual(launchedParams.connectionMode, 'direct'); assert.strictEqual(app.showOpenAIPlanRunModal, false); assert.strictEqual(app.formatOpenAIPlanCell({ openai_plan_type: 'plus' }), 'Plus'); assert.strictEqual(app.formatOpenAIUsagePercent(37.5), '37.5%'); assert.strictEqual(app.statusClass('Token失效(401)'), 'status-登录失败'); assert.strictEqual(app.statusClass('代理认证失败'), 'status-登录失败'); assert.strictEqual(app.statusClass('套餐查询异常:unexpected response body'), 'status-登录失败'); assert(app.accountFilterFields.some(field => field.value === 'openai_plan_type')); const oauthSource = fs.readFileSync(path.join(root, 'src', 'services', 'openai_oauth_service.js'), 'utf8'); const extractionTail = oauthSource.slice(oauthSource.indexOf('async function extractOpenAIAuthJson')); assert.match(extractionTail, /return buildAuthJson\(callback\.code, tokens\);/); assert.doesNotMatch( extractionTail.slice(0, extractionTail.indexOf('module.exports')), /queryAndEnrichOpenAIAuthJson(?:Direct)?|enrichOpenAIAuthJson/, '提取授权 JSON 不得再自动查询套餐', ); console.log('openai_plan_feature.test.js: PASS'); })().catch(error => { console.error(error); process.exitCode = 1; });