const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); function loadModules(fetchImpl) { const cpaSource = fs.readFileSync('background/cpa-api.js', 'utf8'); const stepSource = fs.readFileSync('background/steps/sync-cpa-session.js', 'utf8'); const scope = {}; new Function('self', 'fetch', `${cpaSource}\n${stepSource}; return self;`)(scope, fetchImpl); return scope; } function base64UrlJson(value) { return Buffer.from(JSON.stringify(value), 'utf8') .toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/g, ''); } function jwt(payload) { return `${base64UrlJson({ alg: 'RS256', typ: 'JWT' })}.${base64UrlJson(payload)}.signature`; } test('step 10 reads ChatGPT session and imports CPA auth JSON', async () => { const accessToken = jwt({ exp: 1893456000, 'https://api.openai.com/auth': { chatgpt_account_id: 'acct_step10', chatgpt_plan_type: 'plus', }, 'https://api.openai.com/profile': { email: 'step10@example.com', }, }); const fetchCalls = []; const fetchImpl = async (url, options = {}) => { fetchCalls.push({ url, options }); if (url === 'https://chatgpt.com/api/auth/session') { return { ok: true, status: 200, text: async () => JSON.stringify({ accessToken, user: { email: 'step10@example.com' }, }), }; } if (url === 'https://cpa.example.com/v0/management/auth-files?name=codex-step10%40example.com-plus.json') { return { ok: true, status: 200, json: async () => ({ ok: true }), }; } throw new Error(`unexpected fetch: ${url}`); }; const scope = loadModules(fetchImpl); const events = { completed: null, logs: [], }; const executor = scope.MultiPageBackgroundCpaSessionSync.createCpaSessionSyncExecutor({ addLog: async (message, level = 'info') => events.logs.push({ message, level }), completeStepFromBackground: async (step, payload) => { events.completed = { step, payload }; }, createCpaApi: scope.MultiPageBackgroundCpaApi.createCpaApi, fetchImpl, getPanelMode: (state) => state.panelMode || 'cpa', }); await executor.executeStep10({ panelMode: 'cpa', vpsUrl: 'https://cpa.example.com/management.html#/oauth', vpsPassword: 'secret', }); assert.equal(events.completed.step, 10); assert.deepEqual(events.completed.payload, { verifiedStatus: 'CPA 会话导入完成:step10@example.com', cpaImportedFileName: 'codex-step10@example.com-plus.json', cpaImportedEmail: 'step10@example.com', }); assert.equal(fetchCalls.length, 2); assert.equal(fetchCalls[0].url, 'https://chatgpt.com/api/auth/session'); assert.equal(fetchCalls[1].options.headers.Authorization, 'Bearer secret'); assert.equal(JSON.parse(fetchCalls[1].options.body).access_token, accessToken); }); test('step 10 skips CPA sync in sub2api mode', async () => { const scope = loadModules(async () => { throw new Error('fetch should not be called when sub2api is selected'); }); const events = { completed: null, logs: [], }; const executor = scope.MultiPageBackgroundCpaSessionSync.createCpaSessionSyncExecutor({ addLog: async (message, level = 'info') => events.logs.push({ message, level }), completeStepFromBackground: async (step, payload) => { events.completed = { step, payload }; }, createCpaApi: scope.MultiPageBackgroundCpaApi.createCpaApi, getPanelMode: () => 'sub2api', }); await executor.executeStep10({ panelMode: 'sub2api' }); assert.deepEqual(events.completed, { step: 10, payload: { cpaSyncSkipped: true, cpaSyncSkipReason: 'sub2api-mode', }, }); assert.equal(events.logs.some((entry) => /跳过 CPA session 同步/.test(entry.message)), true); });