Files
FlowPilot/tests/background-luckmail.test.js
T
2026-04-29 22:12:28 +08:00

804 lines
24 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const source = fs.readFileSync('background.js', 'utf8');
function extractFunction(name) {
const markers = [`async function ${name}(`, `function ${name}(`];
const start = markers
.map((marker) => source.indexOf(marker))
.find((index) => index >= 0);
if (start < 0) {
throw new Error(`missing function ${name}`);
}
let parenDepth = 0;
let signatureEnded = false;
let braceStart = -1;
for (let i = start; i < source.length; i += 1) {
const ch = source[i];
if (ch === '(') {
parenDepth += 1;
} else if (ch === ')') {
parenDepth -= 1;
if (parenDepth === 0) {
signatureEnded = true;
}
} else if (ch === '{' && signatureEnded) {
braceStart = i;
break;
}
}
if (braceStart < 0) {
throw new Error(`missing body for function ${name}`);
}
let depth = 0;
let end = braceStart;
for (; end < source.length; end += 1) {
const ch = source[end];
if (ch === '{') depth += 1;
if (ch === '}') {
depth -= 1;
if (depth === 0) {
end += 1;
break;
}
}
}
return source.slice(start, end);
}
test('ensureLuckmailPurchaseForFlow buys openai mailbox and defaults email type to ms_graph', async () => {
const bundle = [
extractFunction('getLuckmailSessionConfig'),
extractFunction('getCurrentLuckmailPurchase'),
extractFunction('ensureLuckmailPurchaseForFlow'),
].join('\n');
const factory = new Function('initialState', `
let currentState = { ...initialState };
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
const purchaseCalls = [];
const activateCalls = [];
function normalizeLuckmailBaseUrl(value) {
return String(value || '').trim() || 'https://mails.luckyous.com';
}
function normalizeLuckmailEmailType(value) {
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
? String(value || '').trim()
: 'ms_graph';
}
function normalizeLuckmailPurchase(value) {
return value;
}
function normalizeLuckmailPurchases(value) {
return value.purchases || [];
}
async function getState() {
return currentState;
}
function createLuckmailClient() {
return {
user: {
async purchaseEmails(projectCode, quantity, options) {
purchaseCalls.push({ projectCode, quantity, options });
return {
purchases: [{ id: 15, email_address: 'demo@outlook.com', token: 'tok-1' }],
};
},
},
};
}
async function findReusableLuckmailPurchaseForFlow() {
return null;
}
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
activateCalls.push({ state, purchase, options });
currentState.currentLuckmailPurchase = purchase;
currentState.email = purchase.email_address;
return purchase;
}
${bundle}
return {
ensureLuckmailPurchaseForFlow,
snapshot() {
return { currentState, purchaseCalls, activateCalls };
},
};
`);
const api = factory({
luckmailApiKey: 'sk-test',
luckmailBaseUrl: '',
luckmailEmailType: '',
luckmailDomain: '',
currentLuckmailPurchase: null,
email: null,
});
const purchase = await api.ensureLuckmailPurchaseForFlow();
const snapshot = api.snapshot();
assert.equal(purchase.email_address, 'demo@outlook.com');
assert.deepStrictEqual(snapshot.purchaseCalls, [{
projectCode: 'openai',
quantity: 1,
options: {
emailType: 'ms_graph',
domain: undefined,
},
}]);
assert.equal(snapshot.activateCalls[0].options.initializeCursor, false);
assert.equal(snapshot.currentState.email, 'demo@outlook.com');
});
test('ensureLuckmailPurchaseForFlow reuses reusable openai mailbox before buying a new one', async () => {
const bundle = [
extractFunction('getLuckmailSessionConfig'),
extractFunction('getCurrentLuckmailPurchase'),
extractFunction('ensureLuckmailPurchaseForFlow'),
].join('\n');
const factory = new Function('initialState', `
let currentState = { ...initialState };
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
const purchaseCalls = [];
const activateCalls = [];
function normalizeLuckmailBaseUrl(value) {
return String(value || '').trim() || 'https://mails.luckyous.com';
}
function normalizeLuckmailEmailType(value) {
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
? String(value || '').trim()
: 'ms_graph';
}
function normalizeLuckmailPurchase(value) {
return value;
}
function normalizeLuckmailPurchases(value) {
return value.purchases || [];
}
async function getState() {
return currentState;
}
function createLuckmailClient() {
return {
user: {
async purchaseEmails(projectCode, quantity, options) {
purchaseCalls.push({ projectCode, quantity, options });
return { purchases: [] };
},
},
};
}
async function findReusableLuckmailPurchaseForFlow() {
return {
id: 99,
email_address: 'reuse@outlook.com',
token: 'tok-reuse',
};
}
async function activateLuckmailPurchaseForFlow(state, client, purchase, options) {
activateCalls.push({ state, purchase, options });
currentState.currentLuckmailPurchase = purchase;
currentState.email = purchase.email_address;
return purchase;
}
${bundle}
return {
ensureLuckmailPurchaseForFlow,
snapshot() {
return { currentState, purchaseCalls, activateCalls };
},
};
`);
const api = factory({
luckmailApiKey: 'sk-test',
luckmailBaseUrl: 'https://mails.luckyous.com',
luckmailEmailType: 'ms_imap',
luckmailDomain: 'outlook.com',
currentLuckmailPurchase: null,
email: null,
});
const purchase = await api.ensureLuckmailPurchaseForFlow();
const snapshot = api.snapshot();
assert.equal(purchase.id, 99);
assert.deepStrictEqual(snapshot.purchaseCalls, []);
assert.equal(snapshot.activateCalls[0].options.initializeCursor, true);
assert.match(snapshot.activateCalls[0].options.logMessage, /已复用 openai 邮箱/);
});
test('activateLuckmailPurchaseForFlow builds baseline cursor from existing mails when reusing mailbox', async () => {
const bundle = extractFunction('activateLuckmailPurchaseForFlow');
const factory = new Function(`
let currentPurchase = null;
let currentCursor = null;
let currentEmail = null;
const buildCalls = [];
function normalizeLuckmailPurchase(value) {
return value;
}
async function setLuckmailPurchaseState(value) {
currentPurchase = value;
}
async function setLuckmailMailCursorState(value) {
currentCursor = value;
}
async function setEmailState(value) {
currentEmail = value;
}
async function addLog() {}
function buildLuckmailBaselineCursor(mails) {
buildCalls.push(mails);
return { messageId: 'mail-new', receivedAt: '2026-04-14 13:32:05' };
}
${bundle}
return {
activateLuckmailPurchaseForFlow,
snapshot() {
return { currentPurchase, currentCursor, currentEmail, buildCalls };
},
};
`);
const api = factory();
const client = {
user: {
async getTokenMails() {
return {
mails: [
{ message_id: 'mail-old', received_at: '2026-04-14 13:31:15' },
{ message_id: 'mail-new', received_at: '2026-04-14 13:32:05' },
],
};
},
},
};
await api.activateLuckmailPurchaseForFlow({}, client, {
id: 5,
email_address: 'reuse@outlook.com',
token: 'tok-reuse',
}, {
initializeCursor: true,
logMessage: 'reuse',
});
const snapshot = api.snapshot();
assert.equal(snapshot.currentPurchase.id, 5);
assert.deepStrictEqual(snapshot.currentCursor, {
messageId: 'mail-new',
receivedAt: '2026-04-14 13:32:05',
});
assert.equal(snapshot.currentEmail, 'reuse@outlook.com');
assert.equal(snapshot.buildCalls.length, 1);
});
test('pollLuckmailVerificationCode snapshots existing mails before accepting new LuckMail code', async () => {
const bundle = extractFunction('pollLuckmailVerificationCode');
const factory = new Function(`
let currentState = {
currentLuckmailPurchase: {
id: 7,
email_address: 'luck@example.com',
token: 'tok-luck',
},
currentLuckmailMailCursor: null,
};
const logs = [];
const cursorWrites = [];
let tokenCodeCalls = 0;
function getCurrentLuckmailPurchase(state) {
return state.currentLuckmailPurchase;
}
function createLuckmailClient() {
return {
user: {
async getTokenMails() {
if (tokenCodeCalls === 0) {
return {
mails: [
{ message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
],
};
}
return {
mails: [
{ message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
{ message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
],
};
},
async getTokenCode() {
tokenCodeCalls += 1;
return tokenCodeCalls === 1
? {
has_new_mail: true,
verification_code: '111111',
mail: { message_id: 'old-mail', received_at: '2026-04-14 13:31:15', verification_code: '111111' },
}
: {
has_new_mail: true,
verification_code: '222222',
mail: { message_id: 'new-mail', received_at: '2026-04-14 13:32:05', verification_code: '222222' },
};
},
async getTokenMailDetail(_token, messageId) {
return { message_id: messageId, received_at: '2026-04-14 13:32:05', verification_code: '222222' };
},
},
};
}
async function getState() {
return currentState;
}
async function setLuckmailMailCursorState(cursor) {
currentState = { ...currentState, currentLuckmailMailCursor: cursor };
cursorWrites.push(cursor);
}
function normalizeLuckmailMailCursor(cursor) {
return {
messageId: cursor?.messageId || cursor?.message_id || '',
receivedAt: cursor?.receivedAt || cursor?.received_at || '',
};
}
function normalizeLuckmailTimestamp(value) {
return Date.parse(String(value || '').replace(' ', 'T') + 'Z') || 0;
}
function buildLuckmailMailCursor(mail) {
return { messageId: mail.message_id || '', receivedAt: mail.received_at || '' };
}
function buildLuckmailBaselineCursor(mails) {
const latest = mails[0] || null;
return latest ? buildLuckmailMailCursor(latest) : null;
}
function isLuckmailMailNewerThanCursor(mail, cursor) {
if (!cursor?.messageId && !cursor?.receivedAt) return true;
if (mail.message_id === cursor.messageId) return false;
return normalizeLuckmailTimestamp(mail.received_at) > normalizeLuckmailTimestamp(cursor.receivedAt);
}
function pickLuckmailVerificationMail(mails, filters) {
const excludeCodes = new Set(filters.excludeCodes || []);
for (const mail of mails || []) {
if (!mail.verification_code || excludeCodes.has(mail.verification_code)) continue;
return { mail, code: mail.verification_code };
}
return null;
}
function normalizeLuckmailTokenCode(result) {
return result;
}
async function resolveLuckmailVerificationMail(client, token, filters, tokenCodeResult) {
if (tokenCodeResult?.mail) {
const inline = pickLuckmailVerificationMail([tokenCodeResult.mail], filters);
if (inline) return inline;
}
const mailList = await client.user.getTokenMails(token);
return pickLuckmailVerificationMail(mailList.mails, filters);
}
async function addLog(message, level) {
logs.push({ message, level });
}
function throwIfStopped() {}
function isStopError() {
return false;
}
async function sleepWithStop() {}
${bundle}
return {
pollLuckmailVerificationCode,
snapshot() {
return { currentState, cursorWrites, logs, tokenCodeCalls };
},
};
`);
const api = factory();
const result = await api.pollLuckmailVerificationCode(4, await api.snapshot().currentState, {
maxAttempts: 2,
intervalMs: 1000,
senderFilters: ['openai'],
subjectFilters: ['code'],
excludeCodes: [],
});
assert.equal(result.code, '222222');
const snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.cursorWrites[0], {
messageId: 'old-mail',
receivedAt: '2026-04-14 13:31:15',
});
assert.deepStrictEqual(snapshot.cursorWrites.at(-1), {
messageId: 'new-mail',
receivedAt: '2026-04-14 13:32:05',
});
assert.equal(snapshot.logs.some((entry) => /已保存当前邮箱旧邮件快照/.test(entry.message)), true);
assert.equal(snapshot.tokenCodeCalls, 2);
});
test('buildPersistentSettingsPayload keeps LuckMail config fields for storage.local persistence', () => {
const bundle = [
extractFunction('normalizePersistentSettingValue'),
extractFunction('buildPersistentSettingsPayload'),
].join('\n');
const factory = new Function(`
const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
const PERSISTED_SETTING_DEFAULTS = {
luckmailApiKey: '',
luckmailBaseUrl: DEFAULT_LUCKMAIL_BASE_URL,
luckmailEmailType: DEFAULT_LUCKMAIL_EMAIL_TYPE,
luckmailDomain: '',
};
const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
function normalizeLuckmailBaseUrl(value) {
const normalized = String(value || '').trim() || DEFAULT_LUCKMAIL_BASE_URL;
return normalized.replace(/\\/$/, '');
}
function normalizeLuckmailEmailType(value) {
return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())
? String(value || '').trim()
: DEFAULT_LUCKMAIL_EMAIL_TYPE;
}
function resolveLegacyAutoStepDelaySeconds() {
return undefined;
}
${bundle}
return {
buildPersistentSettingsPayload,
};
`);
const api = factory();
const payload = api.buildPersistentSettingsPayload({
luckmailApiKey: 'sk-live-demo',
luckmailBaseUrl: 'https://demo.example.com/',
luckmailEmailType: 'ms_imap',
luckmailDomain: ' outlook.com ',
});
assert.deepStrictEqual(payload, {
luckmailApiKey: 'sk-live-demo',
luckmailBaseUrl: 'https://demo.example.com',
luckmailEmailType: 'ms_imap',
luckmailDomain: 'outlook.com',
});
});
test('listLuckmailPurchasesByProject only keeps openai purchases', async () => {
const bundle = extractFunction('listLuckmailPurchasesByProject');
const factory = new Function(`
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
function normalizeLuckmailProjectName(value) {
return String(value || '').trim().toLowerCase();
}
function isLuckmailPurchaseForProject(purchase, projectCode) {
return normalizeLuckmailProjectName(purchase.project_name || purchase.project) === normalizeLuckmailProjectName(projectCode);
}
async function getAllLuckmailPurchases() {
return [
{ id: 1, project_name: 'OpenAi' },
{ id: 2, project_name: 'other' },
{ id: 3, project: 'openai' },
];
}
${bundle}
return { listLuckmailPurchasesByProject };
`);
const api = factory();
const result = await api.listLuckmailPurchasesByProject({}, { projectCode: 'openai' });
assert.deepStrictEqual(result.map((item) => item.id), [1, 3]);
});
test('disableUsedLuckmailPurchases only disables locally used and non-preserved openai mailboxes', async () => {
const bundle = extractFunction('disableUsedLuckmailPurchases');
const factory = new Function(`
let clearedOptions = null;
const disabledCalls = [];
const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
function normalizeLuckmailPurchaseId(value) {
const numeric = Number(value);
return Number.isFinite(numeric) && numeric > 0 ? String(Math.floor(numeric)) : '';
}
async function ensureManualInteractionAllowed() {
return {
luckmailUsedPurchases: { 1: true, 2: true, 3: true },
luckmailPreserveTagId: 9,
luckmailPreserveTagName: '保留',
mailProvider: 'luckmail-api',
};
}
function getLuckmailUsedPurchases(state) {
return state.luckmailUsedPurchases;
}
function getLuckmailPreserveTagInfo(state) {
return {
id: state.luckmailPreserveTagId,
name: state.luckmailPreserveTagName,
};
}
function isLuckmailPurchasePreserved(purchase, options) {
return purchase.tag_id === options.preserveTagId || purchase.tag_name === options.preserveTagName;
}
function createLuckmailClient() {
return {
user: {
async batchSetPurchaseDisabled(ids, disabled) {
disabledCalls.push({ ids, disabled });
},
},
};
}
async function listLuckmailPurchasesByProject() {
return [
{ id: 1, email_address: 'used-1@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
{ id: 2, email_address: 'preserved@outlook.com', user_disabled: 0, tag_id: 9, tag_name: '保留' },
{ id: 3, email_address: 'already-disabled@outlook.com', user_disabled: 1, tag_id: 0, tag_name: '' },
{ id: 4, email_address: 'unused@outlook.com', user_disabled: 0, tag_id: 0, tag_name: '' },
];
}
async function getState() {
return {
currentLuckmailPurchase: { id: 1 },
mailProvider: 'luckmail-api',
};
}
function getCurrentLuckmailPurchase(state) {
return state.currentLuckmailPurchase;
}
function isLuckmailProvider(state) {
return state.mailProvider === 'luckmail-api';
}
async function clearLuckmailRuntimeState(options) {
clearedOptions = options;
}
async function addLog() {}
${bundle}
return {
disableUsedLuckmailPurchases,
snapshot() {
return { disabledCalls, clearedOptions };
},
};
`);
const api = factory();
const result = await api.disableUsedLuckmailPurchases();
const snapshot = api.snapshot();
assert.deepStrictEqual(result.disabledIds, [1]);
assert.deepStrictEqual(snapshot.disabledCalls, [{ ids: [1], disabled: 1 }]);
assert.deepStrictEqual(snapshot.clearedOptions, { clearEmail: true });
});
test('resetState preserves LuckMail session config, used map, and preserve tag cache while clearing runtime purchase state', async () => {
const bundle = [
extractFunction('buildContributionModeState'),
extractFunction('resetState'),
].join('\n');
const factory = new Function([
'let cleared = false;',
'let storedPayload = null;',
"const LOG_PREFIX = '[test]';",
"const DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME = '保留';",
'const DEFAULT_STATE = {',
" luckmailApiKey: '',",
" luckmailBaseUrl: 'https://mails.luckyous.com',",
" luckmailEmailType: 'ms_graph',",
" luckmailDomain: '',",
" panelMode: 'cpa',",
' luckmailUsedPurchases: {},',
' luckmailPreserveTagId: 0,',
" luckmailPreserveTagName: '保留',",
" currentLuckmailPurchase: { token: 'stale' },",
" currentLuckmailMailCursor: { messageId: 'stale' },",
' currentPhoneActivation: null,',
' reusablePhoneActivation: null,',
' email: null,',
'};',
'const CONTRIBUTION_RUNTIME_DEFAULTS = {',
' contributionMode: false,',
" contributionSessionId: '',",
" contributionAuthUrl: '',",
" contributionAuthState: '',",
" contributionCallbackUrl: '',",
" contributionStatus: '',",
" contributionStatusMessage: '',",
' contributionLastPollAt: 0,',
" contributionCallbackStatus: 'idle',",
" contributionCallbackMessage: '',",
' contributionAuthOpenedAt: 0,',
' contributionAuthTabId: 0,',
'};',
'const CONTRIBUTION_RUNTIME_KEYS = Object.keys(CONTRIBUTION_RUNTIME_DEFAULTS);',
'function normalizeLuckmailBaseUrl(value) {',
" const normalized = String(value || '').trim() || 'https://mails.luckyous.com';",
" return normalized.replace(/\\/$/, '');",
'}',
'function normalizeLuckmailEmailType(value) {',
" return ['self_built', 'ms_imap', 'ms_graph', 'google_variant'].includes(String(value || '').trim())",
" ? String(value || '').trim()",
" : 'ms_graph';",
'}',
'function normalizeLuckmailUsedPurchases(value) {',
' return value || {};',
'}',
'async function getPersistedSettings() {',
" return { mailProvider: '163' };",
'}',
'async function getPersistedAliasState() {',
' return {};',
'}',
'const chrome = {',
' storage: {',
' session: {',
' async get() {',
' return {',
" seenCodes: ['seen-1'],",
" seenInbucketMailIds: ['mail-1'],",
" accounts: [{ email: 'saved@example.com' }],",
" tabRegistry: { foo: { tabId: 1 } },",
" sourceLastUrls: { foo: 'https://example.com' },",
" reusablePhoneActivation: { activationId: 'rx-001', phoneNumber: '66951112222', provider: 'hero-sms', serviceCode: 'dr', countryId: 52, successfulUses: 1, maxUses: 3 },",
" luckmailApiKey: 'sk-session',",
" luckmailBaseUrl: 'https://demo.example.com/',",
" luckmailEmailType: 'ms_imap',",
" luckmailDomain: 'outlook.com',",
" luckmailUsedPurchases: { 88: true },",
' luckmailPreserveTagId: 9,',
" luckmailPreserveTagName: '保留',",
' };',
' },',
' async clear() {',
' cleared = true;',
' },',
' async set(payload) {',
' storedPayload = payload;',
' },',
' },',
' },',
'};',
bundle,
'return {',
' resetState,',
' snapshot() {',
' return { cleared, storedPayload };',
' },',
'};',
].join('\n'));
const api = factory();
await api.resetState();
const snapshot = api.snapshot();
assert.equal(snapshot.cleared, true);
assert.equal(snapshot.storedPayload.luckmailApiKey, 'sk-session');
assert.equal(snapshot.storedPayload.luckmailBaseUrl, 'https://demo.example.com');
assert.equal(snapshot.storedPayload.luckmailEmailType, 'ms_imap');
assert.equal(snapshot.storedPayload.luckmailDomain, 'outlook.com');
assert.deepStrictEqual(snapshot.storedPayload.luckmailUsedPurchases, { 88: true });
assert.equal(snapshot.storedPayload.luckmailPreserveTagId, 9);
assert.equal(snapshot.storedPayload.luckmailPreserveTagName, '保留');
assert.equal(snapshot.storedPayload.currentLuckmailPurchase, null);
assert.equal(snapshot.storedPayload.currentLuckmailMailCursor, null);
assert.deepStrictEqual(snapshot.storedPayload.reusablePhoneActivation, {
activationId: 'rx-001',
phoneNumber: '66951112222',
provider: 'hero-sms',
serviceCode: 'dr',
countryId: 52,
successfulUses: 1,
maxUses: 3,
});
assert.equal(snapshot.storedPayload.currentPhoneActivation, null);
});
test('handleStepData step 10 marks current LuckMail purchase as used and clears runtime state', async () => {
const bundle = extractFunction('handleStepData');
const factory = new Function(`
let clearedOptions = null;
let usedMarker = null;
const logs = [];
async function closeLocalhostCallbackTabs() {}
async function getState() {
return {
mailProvider: 'luckmail-api',
currentHotmailAccountId: null,
currentLuckmailPurchase: {
id: 123,
email_address: 'demo@outlook.com',
},
email: 'demo@outlook.com',
};
}
function getCurrentLuckmailPurchase(state) {
return state.currentLuckmailPurchase;
}
function isHotmailProvider() {
return false;
}
async function patchHotmailAccount() {}
function isLuckmailProvider(state) {
return state.mailProvider === 'luckmail-api';
}
async function setLuckmailPurchaseUsedState(purchaseId, used) {
usedMarker = { purchaseId, used };
}
async function clearLuckmailRuntimeState(options) {
clearedOptions = options;
}
async function addLog(message, level) {
logs.push({ message, level });
}
function buildLocalhostCleanupPrefix() {
return '';
}
async function closeTabsByUrlPrefix() {}
function shouldUseCustomRegistrationEmail() {
return false;
}
async function setEmailStateSilently() {}
async function setState() {}
function broadcastDataUpdate() {}
function isLocalhostOAuthCallbackUrl() {
return true;
}
async function finalizePhoneActivationAfterSuccessfulFlow() {}
async function finalizeIcloudAliasAfterSuccessfulFlow() {}
${bundle}
return {
handleStepData,
snapshot() {
return { clearedOptions, usedMarker, logs };
},
};
`);
const api = factory();
await api.handleStepData(10, {
localhostUrl: 'http://localhost:1455/auth/callback?code=abc&state=xyz',
});
const snapshot = api.snapshot();
assert.deepStrictEqual(snapshot.usedMarker, { purchaseId: 123, used: true });
assert.deepStrictEqual(snapshot.clearedOptions, { clearEmail: true });
assert.equal(snapshot.logs.at(-1).message, '当前 LuckMail 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。');
});