重构修改2925获取邮箱
This commit is contained in:
@@ -13,6 +13,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
executeStep7: 0,
|
||||
sleep: [],
|
||||
resolveOptions: null,
|
||||
setStates: [],
|
||||
};
|
||||
const realDateNow = Date.now;
|
||||
Date.now = () => 123456;
|
||||
@@ -29,7 +30,7 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
ensureStep8VerificationPageReady: async (options) => {
|
||||
calls.ensureReady += 1;
|
||||
calls.ensureReadyOptions.push(options || null);
|
||||
return { state: 'verification_page' };
|
||||
return { state: 'verification_page', displayedEmail: 'display.user@example.com' };
|
||||
},
|
||||
executeStep7: async () => {
|
||||
calls.executeStep7 += 1;
|
||||
@@ -53,7 +54,9 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
calls.resolveOptions = options;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
setState: async (payload) => {
|
||||
calls.setStates.push(payload);
|
||||
},
|
||||
setStepStatus: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
sleepWithStop: async (ms) => {
|
||||
@@ -82,6 +85,10 @@ test('step 8 submits login verification directly without replaying step 7', asyn
|
||||
assert.equal(typeof calls.resolveOptions.getRemainingTimeMs, 'function');
|
||||
assert.equal(await calls.resolveOptions.getRemainingTimeMs({ actionLabel: '登录验证码流程' }), 5000);
|
||||
assert.equal(calls.resolveOptions.resendIntervalMs, 25000);
|
||||
assert.equal(calls.resolveOptions.targetEmail, 'display.user@example.com');
|
||||
assert.deepStrictEqual(calls.setStates, [
|
||||
{ step8VerificationTargetEmail: 'display.user@example.com' },
|
||||
]);
|
||||
assert.deepStrictEqual(calls.ensureReadyOptions, [
|
||||
{ timeoutMs: 5000 },
|
||||
]);
|
||||
@@ -136,10 +143,62 @@ test('step 8 disables resend interval for 2925 mailbox polling', async () => {
|
||||
});
|
||||
|
||||
assert.equal(capturedOptions.resendIntervalMs, 0);
|
||||
assert.equal(capturedOptions.targetEmail, '');
|
||||
assert.equal(capturedOptions.beforeSubmit, undefined);
|
||||
assert.equal(typeof capturedOptions.getRemainingTimeMs, 'function');
|
||||
});
|
||||
|
||||
test('step 8 falls back to the run email when the verification page does not expose a displayed email', async () => {
|
||||
let capturedOptions = null;
|
||||
|
||||
const executor = api.createStep8Executor({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
confirmCustomVerificationStepBypass: async () => {},
|
||||
ensureStep8VerificationPageReady: async () => ({ state: 'verification_page', displayedEmail: '' }),
|
||||
executeStep7: async () => {},
|
||||
getOAuthFlowRemainingMs: async () => 8000,
|
||||
getOAuthFlowStepTimeoutMs: async (defaultTimeoutMs) => Math.min(defaultTimeoutMs, 8000),
|
||||
getMailConfig: () => ({
|
||||
provider: 'qq',
|
||||
label: 'QQ 邮箱',
|
||||
source: 'mail-qq',
|
||||
url: 'https://mail.qq.com',
|
||||
navigateOnReuse: false,
|
||||
}),
|
||||
getState: async () => ({ email: 'user@example.com', password: 'secret' }),
|
||||
getTabId: async (sourceName) => (sourceName === 'signup-page' ? 1 : 2),
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isTabAlive: async () => true,
|
||||
isVerificationMailPollingError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
resolveVerificationStep: async (_step, _state, _mail, options) => {
|
||||
capturedOptions = options;
|
||||
},
|
||||
reuseOrCreateTab: async () => {},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
shouldUseCustomRegistrationEmail: () => false,
|
||||
sleepWithStop: async () => {},
|
||||
STANDARD_MAIL_VERIFICATION_RESEND_INTERVAL_MS: 25000,
|
||||
STEP7_MAIL_POLLING_RECOVERY_MAX_ATTEMPTS: 8,
|
||||
throwIfStopped: () => {},
|
||||
});
|
||||
|
||||
await executor.executeStep8({
|
||||
email: 'user@example.com',
|
||||
password: 'secret',
|
||||
oauthUrl: 'https://oauth.example/latest',
|
||||
});
|
||||
|
||||
assert.equal(capturedOptions.targetEmail, 'user@example.com');
|
||||
});
|
||||
|
||||
test('step 8 does not rerun step 7 when verification submit lands on add-phone', async () => {
|
||||
const calls = {
|
||||
executeStep7: 0,
|
||||
|
||||
+245
-49
@@ -51,37 +51,38 @@ function extractFunction(name) {
|
||||
return source.slice(start, end);
|
||||
}
|
||||
|
||||
test('handlePollEmail returns to inbox before initial refresh when 2925 opens on a detail page', async () => {
|
||||
test('handlePollEmail establishes a baseline after opening from detail view and only picks mail from a later refresh', async () => {
|
||||
const bundle = extractFunction('handlePollEmail');
|
||||
|
||||
const api = new Function(`
|
||||
let detailMode = true;
|
||||
let state = 'detail';
|
||||
let refreshCalls = 0;
|
||||
const clickOrder = [];
|
||||
const readAndDeleteCalls = [];
|
||||
const seenCodes = new Set();
|
||||
const mailItem = { text: 'OpenAI verification code 654321' };
|
||||
const baselineMail = { id: 'baseline', text: 'OpenAI newsletter without code' };
|
||||
const newMail = { id: 'new', text: 'OpenAI verification code 654321' };
|
||||
|
||||
function findMailItems() {
|
||||
return detailMode ? [] : [mailItem];
|
||||
if (state === 'detail') return [];
|
||||
if (state === 'baseline') return [baselineMail];
|
||||
return [baselineMail, newMail];
|
||||
}
|
||||
|
||||
function getMailItemId() {
|
||||
return 'mail-1';
|
||||
function getMailItemId(item) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function getCurrentMailIds(items = []) {
|
||||
return new Set(items.map(() => 'mail-1'));
|
||||
}
|
||||
|
||||
function normalizeMinuteTimestamp(value) {
|
||||
return Number(value) || 0;
|
||||
return new Set(items.map((item) => item.id));
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function matchesMailFilters() {
|
||||
return true;
|
||||
function matchesMailFilters(text) {
|
||||
return /openai|verification/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
@@ -93,29 +94,26 @@ function extractVerificationCode(text) {
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function extractEmails() {
|
||||
return [];
|
||||
}
|
||||
|
||||
function emailMatchesTarget() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTargetEmailMatchState() {
|
||||
return { matches: true, hasExplicitEmail: false };
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
|
||||
async function returnToInbox() {
|
||||
clickOrder.push('inbox');
|
||||
detailMode = false;
|
||||
state = 'baseline';
|
||||
return true;
|
||||
}
|
||||
|
||||
async function refreshInbox() {
|
||||
clickOrder.push('refresh');
|
||||
refreshCalls += 1;
|
||||
if (refreshCalls >= 2) {
|
||||
state = 'with-new';
|
||||
}
|
||||
}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item) {
|
||||
readAndDeleteCalls.push(item.id);
|
||||
return item.id === 'new' ? 'Your ChatGPT code is 654321' : 'No code here';
|
||||
}
|
||||
|
||||
function persistSeenCodes() {}
|
||||
@@ -128,38 +126,113 @@ return {
|
||||
getClickOrder() {
|
||||
return clickOrder.slice();
|
||||
},
|
||||
getReadAndDeleteCalls() {
|
||||
return readAndDeleteCalls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.handlePollEmail(4, {
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['verification'],
|
||||
maxAttempts: 1,
|
||||
maxAttempts: 2,
|
||||
intervalMs: 1,
|
||||
filterAfterTimestamp: Date.now(),
|
||||
});
|
||||
|
||||
assert.equal(result.code, '654321');
|
||||
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh']);
|
||||
assert.deepEqual(api.getClickOrder(), ['inbox', 'refresh', 'inbox', 'refresh']);
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['new']);
|
||||
});
|
||||
|
||||
test('handlePollEmail ignores targetEmail and still tests any matching ChatGPT mail', async () => {
|
||||
const bundle = extractFunction('handlePollEmail');
|
||||
|
||||
const api = new Function(`
|
||||
let state = 'empty';
|
||||
const seenCodes = new Set();
|
||||
const readAndDeleteCalls = [];
|
||||
const matchingMail = {
|
||||
id: 'mail-1',
|
||||
text: 'ChatGPT verification code 112233 for another.user@example.com',
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return state === 'ready' ? [matchingMail] : [];
|
||||
}
|
||||
|
||||
function getMailItemId(item) {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function getCurrentMailIds(items = []) {
|
||||
return new Set(items.map((item) => item.id));
|
||||
}
|
||||
|
||||
function parseMailItemTimestamp() {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function matchesMailFilters(text) {
|
||||
return /chatgpt|openai|verification/i.test(String(text || ''));
|
||||
}
|
||||
|
||||
function getMailItemText(item) {
|
||||
return item.text;
|
||||
}
|
||||
|
||||
function extractVerificationCode(text) {
|
||||
const match = String(text || '').match(/(\\d{6})/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
async function returnToInbox() {
|
||||
return true;
|
||||
}
|
||||
async function refreshInbox() {
|
||||
state = 'ready';
|
||||
}
|
||||
|
||||
async function openMailAndDeleteAfterRead(item) {
|
||||
readAndDeleteCalls.push(item.id);
|
||||
return item.text;
|
||||
}
|
||||
|
||||
function persistSeenCodes() {}
|
||||
function log() {}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
handlePollEmail,
|
||||
getReadAndDeleteCalls() {
|
||||
return readAndDeleteCalls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.handlePollEmail(8, {
|
||||
senderFilters: ['chatgpt'],
|
||||
subjectFilters: ['verification'],
|
||||
maxAttempts: 4,
|
||||
intervalMs: 1,
|
||||
targetEmail: 'expected@example.com',
|
||||
});
|
||||
|
||||
assert.equal(result.code, '112233');
|
||||
assert.deepEqual(api.getReadAndDeleteCalls(), ['mail-1']);
|
||||
});
|
||||
|
||||
test('openMailAndGetMessageText always returns to inbox after opening a 2925 message', async () => {
|
||||
const bundle = [
|
||||
extractFunction('findInboxLink'),
|
||||
extractFunction('returnToInbox'),
|
||||
extractFunction('openMailAndGetMessageText'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const MAIL_INBOX_SELECTORS = [
|
||||
'a[href*="mailList"]',
|
||||
'[class*="inbox"]',
|
||||
'[class*="Inbox"]',
|
||||
'[title*="鏀朵欢绠?]',
|
||||
];
|
||||
const clickOrder = [];
|
||||
const mailItem = { kind: 'mail' };
|
||||
const inboxLink = { kind: 'inbox' };
|
||||
let listVisible = true;
|
||||
let bodyText = '';
|
||||
|
||||
@@ -169,18 +242,16 @@ const document = {
|
||||
return bodyText;
|
||||
},
|
||||
},
|
||||
querySelector(selector) {
|
||||
if (selector.includes('mailList') || selector.includes('inbox') || selector.includes('Inbox')) {
|
||||
return inboxLink;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return listVisible ? [mailItem] : [];
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
return { kind: 'inbox' };
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === mailItem) {
|
||||
clickOrder.push('mail');
|
||||
@@ -188,12 +259,8 @@ function simulateClick(node) {
|
||||
bodyText = 'Your ChatGPT code is 731091';
|
||||
return;
|
||||
}
|
||||
if (node === inboxLink) {
|
||||
clickOrder.push('inbox');
|
||||
listVisible = true;
|
||||
return;
|
||||
}
|
||||
throw new Error('unexpected node');
|
||||
clickOrder.push('inbox');
|
||||
listVisible = true;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
@@ -219,3 +286,132 @@ return {
|
||||
assert.deepEqual(api.getClickOrder(), ['mail', 'inbox']);
|
||||
assert.equal(api.isListVisible(), true);
|
||||
});
|
||||
|
||||
test('openMailAndDeleteAfterRead deletes the opened message before returning to inbox', async () => {
|
||||
const bundle = [
|
||||
extractFunction('deleteCurrentMailboxEmail'),
|
||||
extractFunction('returnToInbox'),
|
||||
extractFunction('openMailAndDeleteAfterRead'),
|
||||
].join('\n');
|
||||
|
||||
const api = new Function(`
|
||||
const calls = [];
|
||||
const mailItem = { kind: 'mail' };
|
||||
const deleteButton = { kind: 'delete' };
|
||||
let listVisible = true;
|
||||
let bodyText = '';
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
get textContent() {
|
||||
return bodyText;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function findMailItems() {
|
||||
return listVisible ? [mailItem] : [];
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return deleteButton;
|
||||
}
|
||||
|
||||
function findInboxLink() {
|
||||
return { kind: 'inbox' };
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === mailItem) {
|
||||
calls.push('mail');
|
||||
listVisible = false;
|
||||
bodyText = 'Your ChatGPT code is 778899';
|
||||
return;
|
||||
}
|
||||
if (node === deleteButton) {
|
||||
calls.push('delete');
|
||||
return;
|
||||
}
|
||||
calls.push('inbox');
|
||||
listVisible = true;
|
||||
}
|
||||
|
||||
async function sleep() {}
|
||||
async function sleepRandom() {}
|
||||
const console = { warn() {} };
|
||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
mailItem,
|
||||
openMailAndDeleteAfterRead,
|
||||
getCalls() {
|
||||
return calls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const text = await api.openMailAndDeleteAfterRead(api.mailItem, 8);
|
||||
|
||||
assert.match(text, /778899/);
|
||||
assert.deepEqual(api.getCalls(), ['mail', 'delete', 'inbox']);
|
||||
});
|
||||
|
||||
test('deleteAllMailboxEmails selects all messages and clicks delete', async () => {
|
||||
const bundle = extractFunction('deleteAllMailboxEmails');
|
||||
|
||||
const api = new Function(`
|
||||
const calls = [];
|
||||
const selectAllControl = { kind: 'select-all' };
|
||||
const deleteButton = { kind: 'delete' };
|
||||
|
||||
async function returnToInbox() {
|
||||
calls.push('inbox');
|
||||
return true;
|
||||
}
|
||||
|
||||
function findSelectAllControl() {
|
||||
return selectAllControl;
|
||||
}
|
||||
|
||||
function isCheckboxChecked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function findDeleteButton() {
|
||||
return deleteButton;
|
||||
}
|
||||
|
||||
function simulateClick(node) {
|
||||
if (node === selectAllControl) {
|
||||
calls.push('select-all');
|
||||
return;
|
||||
}
|
||||
if (node === deleteButton) {
|
||||
calls.push('delete');
|
||||
return;
|
||||
}
|
||||
throw new Error('unexpected node');
|
||||
}
|
||||
|
||||
async function sleepRandom() {}
|
||||
|
||||
const console = { warn() {} };
|
||||
const MAIL2925_PREFIX = '[MultiPage:mail-2925]';
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
deleteAllMailboxEmails,
|
||||
getCalls() {
|
||||
return calls.slice();
|
||||
},
|
||||
};
|
||||
`)();
|
||||
|
||||
const result = await api.deleteAllMailboxEmails(4);
|
||||
|
||||
assert.equal(result, true);
|
||||
assert.deepEqual(api.getCalls(), ['inbox', 'select-all', 'delete']);
|
||||
});
|
||||
|
||||
@@ -51,6 +51,8 @@ function extractFunction(name) {
|
||||
}
|
||||
|
||||
const bundle = [
|
||||
extractFunction('getPageTextSnapshot'),
|
||||
extractFunction('getLoginVerificationDisplayedEmail'),
|
||||
extractFunction('inspectLoginAuthState'),
|
||||
extractFunction('normalizeStep6Snapshot'),
|
||||
].join('\n');
|
||||
@@ -62,6 +64,13 @@ const location = {
|
||||
pathname: ${JSON.stringify(overrides.pathname || '/log-in')},
|
||||
};
|
||||
|
||||
const document = {
|
||||
body: {
|
||||
innerText: ${JSON.stringify(overrides.pageText || '')},
|
||||
textContent: ${JSON.stringify(overrides.pageText || '')},
|
||||
},
|
||||
};
|
||||
|
||||
function getLoginTimeoutErrorPageState() {
|
||||
return ${JSON.stringify(overrides.retryState || null)};
|
||||
}
|
||||
@@ -127,6 +136,16 @@ return {
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
verificationTarget: { id: 'otp' },
|
||||
pageText: 'We emailed a code to display.user@example.com. Enter it below.',
|
||||
});
|
||||
|
||||
const snapshot = api.inspectLoginAuthState();
|
||||
assert.strictEqual(snapshot.displayedEmail, 'display.user@example.com');
|
||||
}
|
||||
|
||||
{
|
||||
const api = createApi({
|
||||
oauthConsentPage: true,
|
||||
|
||||
@@ -37,8 +37,10 @@ test('verification flow extends 2925 polling window', () => {
|
||||
const step4Payload = helpers.getVerificationPollPayload(4, { email: 'user@example.com', mailProvider: '2925' });
|
||||
const step8Payload = helpers.getVerificationPollPayload(8, { email: 'user@example.com', mailProvider: '2925' });
|
||||
|
||||
assert.equal(step4Payload.filterAfterTimestamp, 0);
|
||||
assert.equal(step4Payload.maxAttempts, 15);
|
||||
assert.equal(step4Payload.intervalMs, 15000);
|
||||
assert.equal(step8Payload.filterAfterTimestamp, 0);
|
||||
assert.equal(step8Payload.maxAttempts, 15);
|
||||
assert.equal(step8Payload.intervalMs, 15000);
|
||||
});
|
||||
@@ -109,6 +111,67 @@ test('verification flow runs beforeSubmit hook before filling the code', async (
|
||||
]);
|
||||
});
|
||||
|
||||
test('verification flow triggers 2925 mailbox cleanup only after code submission succeeds', async () => {
|
||||
const mailMessages = [];
|
||||
|
||||
const helpers = api.createVerificationFlowHelpers({
|
||||
addLog: async () => {},
|
||||
chrome: {
|
||||
tabs: {
|
||||
update: async () => {},
|
||||
},
|
||||
},
|
||||
CLOUDFLARE_TEMP_EMAIL_PROVIDER: 'cloudflare-temp-email',
|
||||
completeStepFromBackground: async () => {},
|
||||
confirmCustomVerificationStepBypassRequest: async () => ({ confirmed: true }),
|
||||
getHotmailVerificationPollConfig: () => ({}),
|
||||
getHotmailVerificationRequestTimestamp: () => 0,
|
||||
getState: async () => ({}),
|
||||
getTabId: async () => 1,
|
||||
HOTMAIL_PROVIDER: 'hotmail-api',
|
||||
isStopError: () => false,
|
||||
LUCKMAIL_PROVIDER: 'luckmail-api',
|
||||
MAIL_2925_VERIFICATION_INTERVAL_MS: 15000,
|
||||
MAIL_2925_VERIFICATION_MAX_ATTEMPTS: 15,
|
||||
pollCloudflareTempEmailVerificationCode: async () => ({}),
|
||||
pollHotmailVerificationCode: async () => ({}),
|
||||
pollLuckmailVerificationCode: async () => ({}),
|
||||
sendToContentScript: async (_source, message) => {
|
||||
if (message.type === 'FILL_CODE') {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
},
|
||||
sendToMailContentScriptResilient: async (_mail, message) => {
|
||||
mailMessages.push(message.type);
|
||||
if (message.type === 'POLL_EMAIL') {
|
||||
return { code: '654321', emailTimestamp: 123 };
|
||||
}
|
||||
return { ok: true };
|
||||
},
|
||||
setState: async () => {},
|
||||
setStepStatus: async () => {},
|
||||
sleepWithStop: async () => {},
|
||||
throwIfStopped: () => {},
|
||||
VERIFICATION_POLL_MAX_ROUNDS: 5,
|
||||
});
|
||||
|
||||
await helpers.resolveVerificationStep(
|
||||
8,
|
||||
{
|
||||
email: 'user@example.com',
|
||||
mailProvider: '2925',
|
||||
lastLoginCode: null,
|
||||
},
|
||||
{ provider: '2925', label: '2925 邮箱' },
|
||||
{}
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
assert.deepStrictEqual(mailMessages, ['POLL_EMAIL', 'DELETE_ALL_EMAILS']);
|
||||
});
|
||||
|
||||
test('verification flow treats add-phone after login code submit as fatal instead of completing step 8', async () => {
|
||||
const events = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user