Files
go_bt/tests/refund_query_feature.test.js

224 lines
8.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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;
});