const assert = require('assert'); const { shouldRotateProxyForFailure } = require('../src/services/proxy_recovery_service'); const { classifyFailure } = require('../src/utils/error_classifier'); const { MAX_CHATGPT_BROWSER_ATTEMPTS, MAX_CHATGPT_LOGIN_WITH_STUCK_BROWSER_ATTEMPTS, MAX_OPENAI_PHONE_BROWSER_ATTEMPTS, isChatGPTRouteError, shouldRestartChatGPTBrowser, buildChatGPTBrowserRetryMessage, } = require('../src/services/chatgpt_restart_policy'); assert.strictEqual( shouldRotateProxyForFailure({ code: 'SIGNIN_REJECTED', shouldRotateProxy: true }, 'SIGNIN_REJECTED'), true, ); assert.strictEqual( shouldRotateProxyForFailure(null, 'SIGNIN_REJECTED'), true, '即使页面元数据丢失,明确的signin/rejected结果也应换代理', ); assert.strictEqual(shouldRotateProxyForFailure(null, 'NETWORK_PROXY'), true); for (const navigationError of [ 'page.goto: net::ERR_FAILED at https://mail.google.com/mail/u/0/#inbox', 'page.goto: net::ERR_ABORTED at https://mail.google.com/mail/u/0/#inbox', 'page.goto: net::ERR_CONNECTION_CLOSED at https://mail.google.com/mail/u/0/#inbox', 'page.reload: net::ERR_ABORTED', ]) { const navigationFailure = classifyFailure({ message: navigationError, taskType: 'refund_query' }); assert.strictEqual(navigationFailure.code, 'NETWORK_PROXY'); assert.strictEqual( shouldRotateProxyForFailure(navigationFailure, navigationFailure.code), true, `${navigationError} 应在退款查询最终失败后自动换代理`, ); } assert.strictEqual(shouldRotateProxyForFailure(null, 'PASSWORD_WRONG'), false); assert.strictEqual(shouldRotateProxyForFailure(null, 'PASSWORD_CHANGED_RECENTLY'), false); assert.strictEqual(shouldRotateProxyForFailure(null, 'TOTP_CODE_WRONG'), false); assert.strictEqual(shouldRotateProxyForFailure(null, 'TOTP_KEY_INCORRECT'), false); assert.strictEqual(shouldRotateProxyForFailure(null, 'PHONE_VERIFY'), false); assert.strictEqual( shouldRotateProxyForFailure({ code: 'CHATGPT_ROUTE_ERROR', restartBrowser: true }, 'CHATGPT_ROUTE_ERROR'), false, 'ChatGPT Route Error 不能触发代理轮换', ); assert.strictEqual( shouldRotateProxyForFailure(null, 'CHATGPT_LOGIN_WITH_STUCK'), true, 'Google 登录中转页在同代理重开一次后应允许换代理', ); assert.strictEqual( shouldRotateProxyForFailure(null, 'CHATGPT_AGE_SUBMIT_STUCK'), true, '年龄表单多次提交仍无进展时应允许换代理重试', ); for (const code of [ 'CHATGPT_SESSION_CF_HTML', 'CHATGPT_SESSION_HTTP_403', 'CHATGPT_SESSION_HTTP_429', 'CHATGPT_SESSION_HTTP_5XX', ]) { assert.strictEqual( shouldRotateProxyForFailure(null, code), true, `${code} 应允许换代理重试`, ); } assert.deepStrictEqual( classifyFailure({ message: 'ChatGPT Route Error 400', taskType: 'chatgpt_register' }).code, 'CHATGPT_ROUTE_ERROR', ); assert.strictEqual(isChatGPTRouteError('login', { code: 'CHATGPT_ROUTE_ERROR', restartBrowser: true }), false); assert.strictEqual(isChatGPTRouteError('chatgpt_register', { code: 'CAPTCHA' }), false); let browserAttempt = 1; let totalRuns = 0; while (true) { totalRuns++; const failure = { code: 'CHATGPT_ROUTE_ERROR', restartBrowser: true }; if (!shouldRestartChatGPTBrowser('chatgpt_register', failure, browserAttempt)) break; browserAttempt++; } assert.strictEqual(MAX_CHATGPT_BROWSER_ATTEMPTS, 3); assert.strictEqual(totalRuns, 3, 'Route Error 总登录次数必须为初次加两次重启'); browserAttempt = 1; totalRuns = 0; const phoneFailure = { code: 'OPENAI_PHONE_REQUIRED' }; while (true) { totalRuns++; if (!shouldRestartChatGPTBrowser('openai_get_auth', phoneFailure, browserAttempt)) break; browserAttempt++; } assert.strictEqual(MAX_OPENAI_PHONE_BROWSER_ATTEMPTS, 3); assert.strictEqual(totalRuns, 3, '手机号要求总授权次数必须为初次加两次重启'); assert.match( buildChatGPTBrowserRetryMessage('openai_get_auth', phoneFailure, 2), /补充手机号.*第2\/3次/, ); browserAttempt = 1; totalRuns = 0; const stuckFailure = { code: 'CHATGPT_LOGIN_WITH_STUCK' }; while (true) { totalRuns++; if (!shouldRestartChatGPTBrowser('chatgpt_register', stuckFailure, browserAttempt)) break; browserAttempt++; } assert.strictEqual(MAX_CHATGPT_LOGIN_WITH_STUCK_BROWSER_ATTEMPTS, 2); assert.strictEqual(totalRuns, 2, '登录中转页卡住时,同一代理只能重开浏览器一次'); assert.strictEqual( shouldRestartChatGPTBrowser('security_chatgpt', stuckFailure, 1), false, '非独立ChatGPT任务不得触发ChatGPT专用浏览器重开', ); const stuckRetryMessage = buildChatGPTBrowserRetryMessage( 'chatgpt_register', stuckFailure, 2, ); assert.match(stuckRetryMessage, /Google 登录中转页卡住/); assert.match(stuckRetryMessage, /当前代理/); assert.match(stuckRetryMessage, /第2\/2次/); assert.doesNotMatch(stuckRetryMessage, /Route Error/); const routeRetryMessage = buildChatGPTBrowserRetryMessage( 'chatgpt_register', { code: 'CHATGPT_ROUTE_ERROR', restartBrowser: true }, 2, ); assert.match(routeRetryMessage, /Route Error/); assert.match(routeRetryMessage, /第2\/3次/); console.log('proxy_retry_policy.test.js: PASS');