224 lines
8.7 KiB
JavaScript
224 lines
8.7 KiB
JavaScript
'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;
|
||
});
|