'use strict'; const assert = require('assert'); const fs = require('fs'); const path = require('path'); const { classifyRefundSubjects, mapLoginFailureToRefundStatus, retryPageOperation, _private, } = require('../src/services/refund_query_service'); (async () => { assert.deepStrictEqual( classifyRefundSubjects(['Your refund from OpenAI OpCo, LLC #3877-6793']).status, '已退款', ); assert.strictEqual( classifyRefundSubjects(['OpenAI ChatGPT - Usage Policy Violation & Deactivation Warning [C-123]']).status, 'gpt警告', ); assert.strictEqual( classifyRefundSubjects(['OpenAI - Access Deactivated [C-456]']).status, 'gpt封号退款失败', ); assert.strictEqual( classifyRefundSubjects(['您收到了一笔来自 OpenAI OpCo, LLC 的退款 #3877-6793']).status, '已退款', '中文退款标题必须识别', ); assert.strictEqual( classifyRefundSubjects(['OpenAI ChatGPT - 使用政策违规和停用警告 [C-123]']).status, 'gpt警告', '中文违规警告标题必须识别', ); assert.strictEqual( classifyRefundSubjects(['OpenAI - 您的访问权限已停用 [C-456]']).status, 'gpt封号退款失败', '中文访问停用标题必须识别', ); assert.strictEqual( classifyRefundSubjects(['OpenAI API - 访问权限已停用 [C-bOXrf90AwRpd]']).status, 'gpt封号退款失败', 'OpenAI API 中文访问停用标题必须识别', ); assert.strictEqual( classifyRefundSubjects(['OpenAI - 访问权限已停用 [C-eizUkbcm0bCv]']).status, 'gpt封号退款失败', 'OpenAI 中文访问停用标题必须识别', ); assert.strictEqual( classifyRefundSubjects([ 'OpenAI ChatGPT - Usage Policy Violation & Deactivation Warning', 'OpenAI - Access Deactivated', ]).status, 'gpt封号退款失败', ); assert.strictEqual( classifyRefundSubjects([ 'OpenAI ChatGPT - Usage Policy Violation & Deactivation Warning', 'OpenAI - Access Deactivated', 'Your refund from OpenAI OpCo #1234', ]).status, '已退款', '退款邮件必须拥有最高优先级', ); assert.strictEqual(classifyRefundSubjects(['ChatGPT - Your new plan']).status, null); assert.strictEqual(mapLoginFailureToRefundStatus('Wrong password', { code: 'PASSWORD_WRONG' }), '已被改密无法查询'); assert.strictEqual(mapLoginFailureToRefundStatus('2fa密钥不正确', { code: 'TOTP_KEY_INCORRECT' }), '已被改2FA无法查询'); assert.strictEqual(mapLoginFailureToRefundStatus("Confirm you’re not a robot", { code: 'CAPTCHA' }), '需要手机号查询'); assert.strictEqual(mapLoginFailureToRefundStatus('账号已被锁定/封停', { code: 'ACCOUNT_LOCKED' }), 'google封号无法查询'); assert.strictEqual(mapLoginFailureToRefundStatus('页面加载失败', { code: 'NETWORK_PROXY' }), null); let attempts = 0; const retryLogs = []; const retryResult = await retryPageOperation(async () => { attempts++; if (attempts < 4) throw new Error('页面尚未加载'); return 'ok'; }, { maxRetries: 3, onRetry: async info => retryLogs.push(info.retry), }); assert.strictEqual(retryResult, 'ok'); assert.strictEqual(attempts, 4, '首次访问失败后应最多再重试3次'); assert.deepStrictEqual(retryLogs, [1, 2, 3]); let captchaAttempts = 0; await assert.rejects( retryPageOperation(async () => { captchaAttempts++; const error = new Error('captcha'); error.code = 'CAPTCHA'; throw error; }, { maxRetries: 3, shouldRetry: error => error.code !== 'CAPTCHA', }), /captcha/, ); assert.strictEqual(captchaAttempts, 1, '人机验证不是页面访问失败,不应重复提交'); assert.strictEqual(_private.PAGE_RETRY_COUNT, 3); { const emptyLocator = { count: async () => 0, nth() { return this; }, first() { return this; }, isVisible: async () => false, innerText: async () => '', }; const directPasswordPage = { url: () => 'https://accounts.google.com/v3/signin/challenge/pwd?continue=https%3A%2F%2Fmail.google.com', locator: selector => selector === 'body' ? { innerText: async () => 'Welcome Enter your password Show password Next' } : emptyLocator, waitForTimeout: async () => {}, }; assert.strictEqual( await _private.waitForPostAccountSelectionState(directPasswordPage, 10), 'password', '点击已保存账号后直接出现的密码页必须等待并识别,不能重新跳回账号选择页', ); } { const subjects = ['OpenAI ChatGPT - 使用政策违规和停用警告 [C-中文]']; const emptySubjectLocator = { allTextContents: async () => [] }; const inboxPage = { locator: selector => selector === 'tr.zA span.bog' ? { allTextContents: async () => subjects } : emptySubjectLocator, evaluate: async () => false, waitForTimeout: async () => {}, }; const direct = await _private.classifyCurrentInbox(inboxPage); assert.strictEqual(direct.status, 'gpt警告', '搜索前应直接扫描当前收件箱标题'); } function pageWithText(text, { signedOutButton = false } = {}) { let clicked = false; const emptyLocator = { count: async () => 0, nth() { return this; }, first() { return this; }, isVisible: async () => false, innerText: async () => '', }; const buttonLocator = { count: async () => signedOutButton ? 1 : 0, nth() { return this; }, first() { return this; }, isVisible: async () => signedOutButton, click: async () => { clicked = true; }, waitFor: async () => {}, innerText: async () => 'Try again', }; const page = { url: () => 'https://accounts.google.com/', locator(selector) { if (selector === 'body') return { innerText: async () => text }; if (/Try again/.test(selector)) return buttonLocator; return emptyLocator; }, waitForLoadState: async () => {}, waitForTimeout: async () => {}, wasClicked: () => clicked, }; return page; } const restrictedPage = pageWithText( 'Review service restriction Access to a service or feature has been restricted. Gmail Entire service unavailable', ); assert.strictEqual(await _private.isGmailServiceRestricted(restrictedPage), true); const signedOutPage = pageWithText( "You're not signed in Your session ended because there was no activity. Try signing in again. Try again", { signedOutButton: true }, ); const signedOutProgress = []; assert.strictEqual( await _private.handleSignedOutSessionGate(signedOutPage, () => {}, message => signedOutProgress.push(message)), true, ); assert.strictEqual(signedOutPage.wasClicked(), true, '会话结束页必须点击 Try again'); assert.match(signedOutProgress.join(' '), /Try again/); const root = path.join(__dirname, '..'); const html = fs.readFileSync(path.join(root, 'public', 'index.html'), 'utf8'); const app = fs.readFileSync(path.join(root, 'public', 'app.js'), 'utf8'); const recipes = fs.readFileSync(path.join(root, 'src', 'pipeline', 'recipes.js'), 'utf8'); const operations = fs.readFileSync(path.join(root, 'src', 'routes', 'operations.js'), 'utf8'); const runner = fs.readFileSync(path.join(root, 'src', 'workers', 'task_runner.js'), 'utf8'); const refundService = fs.readFileSync(path.join(root, 'src', 'services', 'refund_query_service.js'), 'utf8'); assert.match( refundService, /allowIapChallengeContinuation:\s*true/, '退款查询必须允许 IAP 页面继续按实际元素处理', ); const planButton = html.indexOf('runOpenAIQueryPlan()'); const refundButton = html.indexOf('runRefundQuery()'); const rechargeButton = html.indexOf('openRechargeModal()'); assert(planButton >= 0 && refundButton > planButton && rechargeButton > refundButton, '查询退款按钮应位于查询套餐右侧'); assert.match(app, /runRefundQuery\(\)\s*\{\s*this\.runTask\('refund_query'\);/); assert.match(recipes, /refund_query:\s*\[refundQueryStep\]/); assert.match(operations, /updateAccountRefundStatus:\s*\(args\)\s*=>\s*db\.updateAccountRefundStatus/); assert.match(operations, /account_refund_status:/); assert.match(runner, /taskType === 'refund_query'/); assert.match(runner, /taskType === 'refund_query' \? '等待查询退款\.\.\.'/); assert.match(runner, /shouldRotateProxyForFailure\(directMeta/); assert.match( refundService, /const inboxClassified = await classifyCurrentInbox\(page\);[\s\S]*if \(inboxClassified\.status\)[\s\S]*无需搜索[\s\S]*searchOpenAIWithRetry\(page/, '必须先扫描当前收件箱,未命中时才执行 openai 搜索', ); console.log('refund_query_feature.test.js: PASS'); })().catch(error => { console.error(error); process.exitCode = 1; });