const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const source = fs.readFileSync('background.js', 'utf8'); function extractFunction(name) { const markers = [`async function ${name}(`, `function ${name}(`]; const start = markers .map((marker) => source.indexOf(marker)) .find((index) => index >= 0); if (start < 0) { throw new Error(`missing function ${name}`); } let parenDepth = 0; let signatureEnded = false; let braceStart = -1; for (let i = start; i < source.length; i += 1) { const ch = source[i]; if (ch === '(') { parenDepth += 1; } else if (ch === ')') { parenDepth -= 1; if (parenDepth === 0) { signatureEnded = true; } } else if (ch === '{' && signatureEnded) { braceStart = i; break; } } if (braceStart < 0) { throw new Error(`missing body for function ${name}`); } let depth = 0; let end = braceStart; for (; end < source.length; end += 1) { const ch = source[end]; if (ch === '{') depth += 1; if (ch === '}') { depth -= 1; if (depth === 0) { end += 1; break; } } } return source.slice(start, end); } const NODE_COMPAT_HELPERS = ` const FALLBACK_STEP_NODE_IDS = { 1: 'open-chatgpt', 2: 'submit-signup-email', 3: 'fill-password', 4: 'fetch-signup-code', 5: 'fill-profile', 6: 'plus-checkout-create', 7: 'plus-checkout-billing', 8: 'paypal-approve', 9: 'plus-checkout-return', 10: 'oauth-login', 11: 'fetch-login-code', 12: 'confirm-oauth', 13: 'platform-verify', }; function getNodeIdByStepForState(step, state = {}) { if (typeof getStepDefinitionForState === 'function') { const key = String(getStepDefinitionForState(step, state)?.key || '').trim(); if (key) return key; } return FALLBACK_STEP_NODE_IDS[Number(step)] || ''; } function getStepIdByNodeIdForState(nodeId, state = {}) { const normalizedNodeId = String(nodeId || '').trim(); const ids = typeof getStepIdsForState === 'function' ? getStepIdsForState(state) : Object.keys(FALLBACK_STEP_NODE_IDS).map(Number); for (const step of ids) { if (getNodeIdByStepForState(step, state) === normalizedNodeId) { return Number(step); } } for (const [step, fallbackNodeId] of Object.entries(FALLBACK_STEP_NODE_IDS)) { if (fallbackNodeId === normalizedNodeId) { return Number(step); } } return null; } function getNodeIdsForState(state = {}) { const ids = typeof getStepIdsForState === 'function' ? getStepIdsForState(state) : Object.keys(FALLBACK_STEP_NODE_IDS).map(Number); return ids .map((step) => getNodeIdByStepForState(step, state)) .filter(Boolean); } function getNodeDefinitionForState(nodeId, state = {}) { const normalizedNodeId = String(nodeId || '').trim(); const step = getStepIdByNodeIdForState(normalizedNodeId, state); const executeKey = typeof getStepExecutionKeyForState === 'function' ? getStepExecutionKeyForState(step, state) : normalizedNodeId; return normalizedNodeId ? { nodeId: normalizedNodeId, legacyStepId: step, executeKey } : null; } function getNodeTitleForState(nodeId) { return String(nodeId || '').trim(); } function projectStepStatusesToNodeStatuses(stepStatuses = {}, state = {}) { const nodeStatuses = {}; for (const [step, status] of Object.entries(stepStatuses || {})) { const nodeId = getNodeIdByStepForState(step, state); if (nodeId) nodeStatuses[nodeId] = status; } return nodeStatuses; } const rawGetStateForNodeCompat = getState; getState = async function getStateWithNodeStatuses() { const state = await rawGetStateForNodeCompat(); return { ...state, nodeStatuses: { ...projectStepStatusesToNodeStatuses(state?.stepStatuses || {}, state), ...(state?.nodeStatuses || {}), }, }; }; async function executeNodeAndWait(nodeId, delayAfter) { const directStep = Number(nodeId); if (Number.isInteger(directStep) && directStep > 0) { return executeStepAndWait(directStep, delayAfter); } return executeStepAndWait(getStepIdByNodeIdForState(nodeId, await getState()), delayAfter); } function getAutoRunNodeDelayMs() { return 0; } async function runAutoSequenceFromStep(step, context = {}) { return runAutoSequenceFromNode(getNodeIdByStepForState(step, await getState()), context); } `; const bundle = [ extractFunction('isAddPhoneAuthFailure'), extractFunction('isAddPhoneAuthUrl'), extractFunction('isAddPhoneAuthState'), extractFunction('isPlusCheckoutNonFreeTrialFailure'), extractFunction('isGpcCheckoutRestartRequiredFailure'), extractFunction('isPlusCheckoutRestartStep'), extractFunction('isPlusCheckoutRestartRequiredFailure'), extractFunction('getLatestLogTimestamp'), extractFunction('buildAutoRunNodeIdleRestartError'), extractFunction('isAutoRunStepIdleRestartError'), extractFunction('startAutoRunNodeIdleLogWatchdog'), extractFunction('runAutoNodeActionWithIdleLogWatchdog'), extractFunction('executeNodeAndWaitWithAutoRunIdleLogWatchdog'), extractFunction('getDownstreamStateResets'), extractFunction('getPostStep6AutoRestartDecision'), NODE_COMPAT_HELPERS, extractFunction('getAutoRunWorkflowNodeIds'), extractFunction('runAutoSequenceFromNode'), extractFunction('runAutoSequenceFromNodeGraph'), ].join('\n'); const defaultStepDefinitions = { 1: { key: 'open-signup' }, 2: { key: 'prepare-email' }, 3: { key: 'fill-password' }, 4: { key: 'verify-email' }, 5: { key: 'profile-basic' }, 6: { key: 'profile-finish' }, 7: { key: 'oauth-login' }, 8: { key: 'fetch-login-code' }, 9: { key: 'confirm-oauth' }, 10: { key: 'platform-verify' }, }; const PHONE_IDENTITY_STATE_KEYS = [ 'phoneNumber', 'signupPhoneNumber', 'signupPhoneActivation', 'signupPhoneCompletedActivation', 'signupPhoneVerificationRequestedAt', 'signupPhoneVerificationPurpose', 'accountIdentifierType', 'accountIdentifier', ]; function createDownstreamResetHarness(stepKey = '') { return new Function(` function getStepExecutionKeyForState() { return ${JSON.stringify(stepKey)}; } ${extractFunction('getDownstreamStateResets')} return { getDownstreamStateResets }; `)(); } test('downstream restarts after account creation preserve phone signup identity fields', () => { const numericResetHarness = createDownstreamResetHarness(''); const stepKeyResetHarnesses = [ createDownstreamResetHarness('oauth-login'), createDownstreamResetHarness('fetch-login-code'), createDownstreamResetHarness('confirm-oauth'), ]; const phoneState = { accountIdentifierType: 'phone', accountIdentifier: '+447780579093', signupPhoneNumber: '+447780579093', signupPhoneActivation: { activationId: 'active', phoneNumber: '+447780579093' }, signupPhoneCompletedActivation: { activationId: 'done', phoneNumber: '+447780579093' }, }; for (const step of [5, 6, 7, 8, 9]) { const resets = numericResetHarness.getDownstreamStateResets(step, phoneState); for (const key of PHONE_IDENTITY_STATE_KEYS) { assert.equal( Object.prototype.hasOwnProperty.call(resets, key), false, `step ${step} reset must not clear ${key}` ); } } for (const harness of stepKeyResetHarnesses) { const resets = harness.getDownstreamStateResets(10, phoneState); for (const key of PHONE_IDENTITY_STATE_KEYS) { assert.equal( Object.prototype.hasOwnProperty.call(resets, key), false, `${key} must not be cleared by step-key reset` ); } } }); function createHarness(options = {}) { const { startStep = 7, failureStep = 10, failureBudget = 1, failureMessage = '认证失败: Request failed with status code 502', authState = { state: 'password_page', url: 'https://auth.openai.com/log-in' }, customState = {}, stepDefinitions = defaultStepDefinitions, stepIds = Object.keys(stepDefinitions).map(Number).sort((a, b) => a - b), lastStepId = Math.max(...stepIds), finalOAuthChainStartStep = 7, idleLogTimeoutMs = 300000, idleLogCheckIntervalMs = 5000, hangStep = 0, hangBudget = 0, } = options; return new Function(` const AUTO_STEP_DELAYS = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0 }; const LAST_STEP_ID = ${JSON.stringify(lastStepId)}; const FINAL_OAUTH_CHAIN_START_STEP = ${JSON.stringify(finalOAuthChainStartStep)}; const SIGNUP_METHOD_PHONE = 'phone'; const PLUS_PAYMENT_METHOD_GPC_HELPER = 'gpc-helper'; const AUTO_RUN_STEP_IDLE_LOG_TIMEOUT_MS = ${JSON.stringify(idleLogTimeoutMs)}; const AUTO_RUN_STEP_IDLE_LOG_CHECK_INTERVAL_MS = ${JSON.stringify(idleLogCheckIntervalMs)}; const AUTO_RUN_STEP_IDLE_RESTART_MAX_ATTEMPTS = 3; const AUTO_RUN_STEP_IDLE_RESTART_ERROR_PREFIX = 'AUTO_RUN_STEP_IDLE_RESTART::'; const LOG_PREFIX = '[test]'; const chrome = { tabs: { update: async () => {}, }, }; let remainingFailures = ${JSON.stringify(failureBudget)}; let remainingHangs = ${JSON.stringify(hangBudget)}; const events = { steps: [], logs: [], invalidations: [], stateUpdates: [], cancellations: [], stopBroadcasts: 0, }; async function addLog(message, level = 'info') { events.logs.push({ message, level, timestamp: Date.now() }); } async function ensureAutoEmailReady() {} async function ensureResolvedSignupMethodForRun() { return 'email'; } async function broadcastAutoRunStatus() {} async function getState() { return { stepStatuses: { 3: 'completed' }, mailProvider: '163', logs: events.logs, ...${JSON.stringify(customState)}, }; } function getStepIdsForState() { return ${JSON.stringify(stepIds)}; } function getStepDefinitionForState(step) { const map = ${JSON.stringify(stepDefinitions)}; return map[Number(step)] || null; } function getStepExecutionKeyForState(step, state = {}) { return String(getStepDefinitionForState(step, state)?.key || '').trim(); } function isStopError(error) { return (error?.message || String(error || '')) === '流程已被用户停止。'; } function isStepDoneStatus(status) { return status === 'completed' || status === 'manual_completed' || status === 'skipped'; } async function executeStepAndWait(step) { events.steps.push(step); if (step === ${JSON.stringify(hangStep)} && remainingHangs > 0) { remainingHangs -= 1; return new Promise(() => {}); } if (step === ${JSON.stringify(failureStep)} && remainingFailures > 0) { remainingFailures -= 1; throw new Error(${JSON.stringify(failureMessage)}); } } async function getTabId() { return 1; } async function invalidateDownstreamAfterStepRestart(step, options = {}) { events.invalidations.push({ step, options }); const resets = getDownstreamStateResets(step, await getState()); if (Object.keys(resets).length > 0) { events.stateUpdates.push(resets); } } function cancelPendingCommands(reason = '') { events.cancellations.push(reason); } async function broadcastStopToContentScripts() { events.stopBroadcasts += 1; } function getLoginAuthStateLabel(state) { return state || 'unknown'; } function getErrorMessage(error) { return error?.message || String(error || ''); } function normalizePlusPaymentMethod(value = '') { const normalized = String(value || '').trim().toLowerCase(); return normalized === PLUS_PAYMENT_METHOD_GPC_HELPER ? PLUS_PAYMENT_METHOD_GPC_HELPER : normalized; } function isPhoneSmsPlatformRateLimitFailure(error) { const message = getErrorMessage(error); return /FIVE_SIM_RATE_LIMIT::|5sim[\s\S]*(?:限流|rate\s*limit)/i.test(message); } async function getLoginAuthStateFromContent() { return ${JSON.stringify(authState)}; } ${bundle} return { async run() { await runAutoSequenceFromStep(${JSON.stringify(startStep)}, { targetRun: 1, totalRuns: 1, attemptRuns: 1, continued: false, }); return events; }, async runAndCaptureError() { try { await runAutoSequenceFromStep(${JSON.stringify(startStep)}, { targetRun: 1, totalRuns: 1, attemptRuns: 1, continued: false, }); return null; } catch (error) { return { error, events }; } }, }; `)(); } test('auto-run keeps restarting from step 7 after post-login failures without a hard cap', async () => { const harness = createHarness({ failureStep: 10, failureBudget: 6, failureMessage: '认证失败: Request failed with status code 502', authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' }, }); const events = await harness.run(); assert.equal(events.invalidations.length, 6); assert.deepStrictEqual( events.steps, [ 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, ] ); assert.ok(events.logs.some(({ message }) => /回到节点 oauth-login 重新开始授权流程/.test(message))); }); test('auto-run restarts the current step after five minutes without new logs', async () => { const harness = createHarness({ startStep: 10, failureStep: 0, hangStep: 10, hangBudget: 1, idleLogTimeoutMs: 20, idleLogCheckIntervalMs: 5, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [10, 10]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [9]); assert.equal(events.cancellations.length, 1); assert.equal(events.stopBroadcasts, 1); assert.ok(events.logs.some(({ message }) => /5 分钟没有新日志,准备重新开始当前节点/.test(message))); }); test('auto-run applies the idle-log restart watchdog to early steps too', async () => { const harness = createHarness({ startStep: 2, failureStep: 0, hangStep: 2, hangBudget: 1, idleLogTimeoutMs: 20, idleLogCheckIntervalMs: 5, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [2, 2, 4, 5, 6, 7, 8, 9, 10]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [1]); assert.equal(events.cancellations.length, 1); assert.equal(events.stopBroadcasts, 1); }); test('auto-run stops current-step idle restarts after the retry cap', async () => { const harness = createHarness({ startStep: 10, failureStep: 0, hangStep: 10, hangBudget: 4, idleLogTimeoutMs: 20, idleLogCheckIntervalMs: 5, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.match(result.error.message, /AUTO_RUN_STEP_IDLE_RESTART::节点 platform-verify/); assert.deepStrictEqual(result.events.steps, [10, 10, 10, 10]); assert.deepStrictEqual(result.events.invalidations.map((entry) => entry.step), [9, 9, 9]); assert.ok(result.events.logs.some(({ message }) => /已连续 3 次因 5 分钟无新日志而重开/.test(message))); }); test('auto-run stops restarting once add-phone is detected', async () => { const harness = createHarness({ failureStep: 7, failureBudget: 1, failureMessage: '当前页面已进入手机号页面。URL: https://auth.openai.com/add-phone', authState: { state: 'add_phone_page', url: 'https://auth.openai.com/add-phone' }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.equal(result.events.invalidations.length, 0); assert.deepStrictEqual(result.events.steps, [7]); assert.ok(result.events.logs.some(({ message }) => /进入 add-phone/.test(message))); }); test('auto-run stops restarting on generic phone-page failure messages even without add-phone url', async () => { const harness = createHarness({ failureStep: 9, failureBudget: 1, failureMessage: '步骤 8:当前认证页进入手机号页面,当前流程无法继续自动授权。', authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.equal(result.events.invalidations.length, 0); assert.deepStrictEqual(result.events.steps, [7, 8, 9]); assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message))); }); test('auto-run does not restart step 7 when phone verification exhausted replacement attempts in add-phone flow', async () => { const harness = createHarness({ failureStep: 9, failureBudget: 1, failureMessage: 'Step 9: phone verification did not succeed after 3 number replacements. Last reason: sms_timeout_after_resend.', authState: { state: 'add_phone_page', url: 'https://auth.openai.com/add-phone' }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.equal(result.events.invalidations.length, 0); assert.deepStrictEqual(result.events.steps, [7, 8, 9]); assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message))); }); test('auto-run post-login restart decision does not treat 5sim rate limit on add-phone page as add-phone fatal', async () => { const harness = createHarness({ failureStep: 9, failureBudget: 1, failureMessage: 'FIVE_SIM_RATE_LIMIT::5sim 购买接口触发限流,请稍后再试:印度 (India): rate limit。', authState: { state: 'add_phone_page', url: 'https://auth.openai.com/add-phone' }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.equal(result.events.invalidations.length, 0); assert.deepStrictEqual(result.events.steps, [7, 8, 9]); assert.ok(!result.events.logs.some(({ message }) => /进入 add-phone/.test(message))); assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message))); }); test('auto-run stop errors after step 7 are rethrown immediately instead of restarting', async () => { const harness = createHarness({ failureStep: 9, failureBudget: 1, failureMessage: '流程已被用户停止。', authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' }, }); const result = await harness.runAndCaptureError(); assert.equal(result?.error?.message, '流程已被用户停止。'); assert.equal(result.events.invalidations.length, 0); assert.deepStrictEqual(result.events.steps, [7, 8, 9]); assert.ok(!result.events.logs.some(({ message }) => /回到步骤 7 重新开始授权流程/.test(message))); }); test('auto-run restarts from confirm-oauth step after transient step10 token_exchange_user_error', async () => { const harness = createHarness({ failureStep: 10, failureBudget: 1, failureMessage: 'token exchange failed: status 400, body: { "error": { "message": "Invalid request. Please try again later.", "type": "invalid_request_error", "param": null, "code": "token_exchange_user_error" } }', authState: { state: 'oauth_consent_page', url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent' }, customState: { panelMode: 'sub2api', stepStatuses: { 3: 'completed' }, stepsVersion: 'ultra2.0', visibleStep: 10, accountContributionEnabled: false, }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [7, 8, 9, 10, 9, 10]); assert.equal(events.invalidations.length, 1); assert.deepStrictEqual(events.invalidations[0], { step: 8, options: { logLabel: '节点 platform-verify 报错后准备回到 confirm-oauth 重试(第 1 次重开)', }, }); assert.ok(events.logs.some(({ message }) => /回到节点 confirm-oauth 重新开始授权流程/.test(message))); }); test('auto-run restarts SUB2API expired oauth session from oauth-login and clears stale session state', async () => { const harness = createHarness({ failureStep: 10, failureBudget: 1, failureMessage: 'session not found or expired', authState: { state: 'oauth_consent_page', url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent' }, customState: { panelMode: 'sub2api', stepStatuses: { 3: 'completed' }, stepsVersion: 'ultra2.0', visibleStep: 10, sub2apiSessionId: 'expired-session', sub2apiOAuthState: 'expired-state', sub2apiGroupId: 5, sub2apiGroupIds: [5], sub2apiDraftName: 'draft', sub2apiProxyId: 7, accountContributionEnabled: false, }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [7, 8, 9, 10, 7, 8, 9, 10]); assert.equal(events.invalidations.length, 1); assert.deepStrictEqual(events.invalidations[0], { step: 6, options: { logLabel: '节点 platform-verify 报错后准备回到 oauth-login 重试(第 1 次重开)', }, }); const resetPatch = events.stateUpdates.find((updates) => updates.sub2apiSessionId === null); assert.ok(resetPatch, 'expected oauth-login restart to clear stale SUB2API OAuth runtime'); assert.equal(resetPatch.sub2apiOAuthState, null); assert.equal(resetPatch.sub2apiGroupId, null); assert.deepStrictEqual(resetPatch.sub2apiGroupIds, []); assert.equal(resetPatch.sub2apiDraftName, null); assert.equal(resetPatch.sub2apiProxyId, null); assert.ok(events.logs.some(({ message }) => /回到节点 oauth-login 重新开始授权流程/.test(message))); }); test('auto-run restarts Plus/GPC oauth-login aggregate entry-open failure from step 10', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 10, failureStep: 10, failureBudget: 1, failureMessage: '步骤 10:判断失败后已重试 2 次,仍未成功。最后原因:点击登录入口后仍未进入手机号/邮箱/密码/验证码页。', authState: { state: 'entry_page', url: 'https://auth.openai.com/log-in' }, stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed', 6: 'completed', 7: 'completed' }, plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [10, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [7]); assert.ok(events.logs.some(({ message }) => /回到节点 oauth-login 重新开始授权流程/.test(message))); assert.ok(!events.logs.some(({ message }) => /停止自动回到节点 oauth-login 重开/.test(message))); }); test('auto-run restarts Plus/GPC fetch-code and confirm-oauth failures from oauth-login step 10', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; for (const scenario of [ { failureStep: 11, failureMessage: '步骤 11:无法获取新的登录验证码。', expectedSteps: [10, 11, 10, 11, 12, 13], }, { failureStep: 12, failureMessage: '步骤 12:长时间未进入 OAuth 同意页,无法定位“继续”按钮。', expectedSteps: [10, 11, 12, 10, 11, 12, 13], }, ]) { const harness = createHarness({ startStep: 10, failureStep: scenario.failureStep, failureBudget: 1, failureMessage: scenario.failureMessage, authState: { state: 'password_page', url: 'https://auth.openai.com/log-in' }, stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed', 6: 'completed', 7: 'completed' }, plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, scenario.expectedSteps); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [7]); assert.ok(events.logs.some(({ message }) => /回到节点 oauth-login 重新开始授权流程/.test(message))); } }); test('auto-run restarts Plus/GPC transient platform verify exchange failures from confirm-oauth step 12', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 10, failureStep: 13, failureBudget: 1, failureMessage: 'token exchange failed at https://auth.openai.com/oauth/token: token_exchange_user_error: Invalid request. Please try again later.', authState: { state: 'oauth_consent_page', url: 'https://auth.openai.com/sign-in-with-chatgpt/codex/consent' }, stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed', 6: 'completed', 7: 'completed' }, plusModeEnabled: true, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', panelMode: 'sub2api', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [10, 11, 12, 13, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [11]); assert.ok(events.logs.some(({ message }) => /节点 platform-verify.*回到节点 confirm-oauth 重新开始授权流程/.test(message))); }); test('auto-run restarts Plus checkout from step 6 when checkout creation fails', async () => { const plusPaypalSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 8: { key: 'paypal-approve' }, 9: { key: 'plus-checkout-return' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 6, failureBudget: 1, failureMessage: '步骤 6:创建 Plus Checkout 失败:checkout request failed', stepDefinitions: plusPaypalSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusModeEnabled: true, plusPaymentMethod: 'paypal', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [6, 6, 7, 8, 9, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]); assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 Plus Checkout/.test(message))); }); test('auto-run restarts Plus checkout from step 6 when billing fails for non-free-trial reasons', async () => { const plusPaypalSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 8: { key: 'paypal-approve' }, 9: { key: 'plus-checkout-return' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 1, failureMessage: '步骤 7:账单地址 iframe 无法注入,请重新创建 checkout。', stepDefinitions: plusPaypalSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusModeEnabled: true, plusPaymentMethod: 'paypal', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [6, 7, 6, 7, 8, 9, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]); assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新创建 Plus Checkout/.test(message))); }); test('auto-run restarts GPC checkout from step 6 when step 7 page flow stalls', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 2, failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual( events.steps, [6, 7, 6, 7, 6, 7, 10, 11, 12, 13] ); assert.deepStrictEqual( events.invalidations.map((entry) => entry.step), [5, 5] ); assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message))); }); test('auto-run treats GPC account binding as recoverable step 6 restart', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 1, failureMessage: 'GPC_PAGE_FLOW_ENDED::GOPAY已经绑了订阅,需要手动解绑', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [6, 7, 6, 7, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]); }); test('auto-run restarts GPC checkout from step 6 when ChatGPT session cannot be read', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 6, failureBudget: 1, failureMessage: '步骤 6:GPC 模式获取 ChatGPT session 失败。', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [6, 6, 7, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]); assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message))); }); test('auto-run restarts GPC checkout from step 6 when page returns without completion', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 1, failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面已尝试启动 10 次仍未显示订阅完成。', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual(events.steps, [6, 7, 6, 7, 10, 11, 12, 13]); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5]); assert.ok(events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message))); }); test('auto-run keeps rebuilding GPC checkout beyond three failures', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, 11: { key: 'fetch-login-code' }, 12: { key: 'confirm-oauth' }, 13: { key: 'platform-verify' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 4, failureMessage: 'GPC_PAGE_FLOW_ENDED::步骤 7:GPC 页面等待超时,未检测到订阅完成。', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const events = await harness.run(); assert.deepStrictEqual( events.steps, [6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 10, 11, 12, 13] ); assert.deepStrictEqual(events.invalidations.map((entry) => entry.step), [5, 5, 5, 5]); assert.ok(events.logs.some(({ message }) => /第 4 次/.test(message))); }); test('auto-run does not restart GPC checkout when Plus account has no free-trial eligibility', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 1, failureMessage: 'PLUS_CHECKOUT_NON_FREE_TRIAL::步骤 7:今日应付金额不是 0(IDR 299000),当前账号没有免费试用资格,已跳过支付提交。', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.deepStrictEqual(result.events.steps, [6, 7]); assert.equal(result.events.invalidations.length, 0); }); test('auto-run does not restart GPC checkout when account already has a ChatGPT subscription', async () => { const plusGpcSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 10: { key: 'oauth-login' }, }; const harness = createHarness({ startStep: 6, failureStep: 7, failureBudget: 1, failureMessage: 'GPC_PAGE_FLOW_ENDED::该账号已经开通过ChatGPT订阅套餐,不能重复订阅。(checkout_order)', stepDefinitions: plusGpcSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed' }, plusPaymentMethod: 'gpc-helper', plusCheckoutSource: 'gpc-helper', }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.deepStrictEqual(result.events.steps, [6, 7]); assert.equal(result.events.invalidations.length, 0); assert.ok(!result.events.logs.some(({ message }) => /回到节点 plus-checkout-create 重新准备 GPC 页面/.test(message))); }); test('auto-run does not reroute SUB2API session import failures into OAuth restarts', async () => { const plusSessionSteps = { 6: { key: 'plus-checkout-create' }, 7: { key: 'plus-checkout-billing' }, 8: { key: 'paypal-approve' }, 9: { key: 'plus-checkout-return' }, 10: { key: 'sub2api-session-import' }, }; const harness = createHarness({ startStep: 10, failureStep: 10, failureBudget: 1, failureMessage: '步骤 10:当前页面未读取到有效的 ChatGPT session。', stepDefinitions: plusSessionSteps, finalOAuthChainStartStep: 10, customState: { stepStatuses: { 3: 'completed', 6: 'completed', 7: 'completed', 8: 'completed', 9: 'completed', }, plusModeEnabled: true, plusPaymentMethod: 'paypal', panelMode: 'sub2api', plusAccountAccessStrategy: 'sub2api_codex_session', }, }); const result = await harness.runAndCaptureError(); assert.ok(result?.error); assert.match(result.error.message, /未读取到有效的 ChatGPT session/); assert.deepStrictEqual(result.events.steps, [10]); assert.equal(result.events.invalidations.length, 0); assert.ok(!result.events.logs.some(({ message }) => /回到节点 oauth-login|回到节点 confirm-oauth|重新开始授权流程/.test(message))); });