Add LuckMail openai mailbox management
This commit is contained in:
@@ -0,0 +1,568 @@
|
||||
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('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('resetState');
|
||||
|
||||
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: '',",
|
||||
' luckmailUsedPurchases: {},',
|
||||
' luckmailPreserveTagId: 0,',
|
||||
" luckmailPreserveTagName: '保留',",
|
||||
" currentLuckmailPurchase: { token: 'stale' },",
|
||||
" currentLuckmailMailCursor: { messageId: 'stale' },",
|
||||
' email: null,',
|
||||
'};',
|
||||
'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' };",
|
||||
'}',
|
||||
'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' },",
|
||||
" 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);
|
||||
});
|
||||
|
||||
test('handleStepData step 9 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;
|
||||
}
|
||||
|
||||
${bundle}
|
||||
|
||||
return {
|
||||
handleStepData,
|
||||
snapshot() {
|
||||
return { clearedOptions, usedMarker, logs };
|
||||
},
|
||||
};
|
||||
`);
|
||||
|
||||
const api = factory();
|
||||
await api.handleStepData(9, {
|
||||
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 邮箱运行态已清空,下轮将优先复用未用邮箱或重新购买邮箱。');
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const {
|
||||
DEFAULT_LUCKMAIL_BASE_URL,
|
||||
DEFAULT_LUCKMAIL_EMAIL_TYPE,
|
||||
DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
buildLuckmailBaselineCursor,
|
||||
buildLuckmailMailCursor,
|
||||
extractLuckmailVerificationCode,
|
||||
filterReusableLuckmailPurchases,
|
||||
isLuckmailMailNewerThanCursor,
|
||||
isLuckmailPurchaseForProject,
|
||||
normalizeLuckmailBaseUrl,
|
||||
normalizeLuckmailEmailType,
|
||||
normalizeLuckmailProjectName,
|
||||
normalizeLuckmailPurchaseListPage,
|
||||
normalizeLuckmailTags,
|
||||
normalizeLuckmailUsedPurchases,
|
||||
normalizeTimestamp,
|
||||
normalizeLuckmailTokenCode,
|
||||
normalizeLuckmailTokenMail,
|
||||
pickReusableLuckmailPurchase,
|
||||
pickLuckmailVerificationMail,
|
||||
} = require('../luckmail-utils.js');
|
||||
|
||||
test('normalizeLuckmailEmailType keeps supported values and falls back to ms_graph', () => {
|
||||
assert.equal(normalizeLuckmailEmailType('self_built'), 'self_built');
|
||||
assert.equal(normalizeLuckmailEmailType('ms_imap'), 'ms_imap');
|
||||
assert.equal(normalizeLuckmailEmailType('ms_graph'), 'ms_graph');
|
||||
assert.equal(normalizeLuckmailEmailType('google_variant'), 'google_variant');
|
||||
assert.equal(normalizeLuckmailEmailType(''), DEFAULT_LUCKMAIL_EMAIL_TYPE);
|
||||
assert.equal(normalizeLuckmailEmailType('unknown'), DEFAULT_LUCKMAIL_EMAIL_TYPE);
|
||||
});
|
||||
|
||||
test('normalizeLuckmailBaseUrl trims invalid input to default base url', () => {
|
||||
assert.equal(normalizeLuckmailBaseUrl(''), DEFAULT_LUCKMAIL_BASE_URL);
|
||||
assert.equal(normalizeLuckmailBaseUrl('https://mails.luckyous.com/'), DEFAULT_LUCKMAIL_BASE_URL);
|
||||
assert.equal(normalizeLuckmailBaseUrl('https://demo.example.com/api/'), 'https://demo.example.com/api');
|
||||
assert.equal(normalizeLuckmailBaseUrl('notaurl'), DEFAULT_LUCKMAIL_BASE_URL);
|
||||
});
|
||||
|
||||
test('normalizeLuckmailTokenCode and normalizeLuckmailTokenMail extract verification code', () => {
|
||||
const tokenCode = normalizeLuckmailTokenCode({
|
||||
email_address: 'demo@outlook.com',
|
||||
project: 'openai',
|
||||
has_new_mail: true,
|
||||
verification_code: '123456',
|
||||
mail: {
|
||||
message_id: 'mail-1',
|
||||
from: 'noreply@openai.com',
|
||||
subject: 'Your ChatGPT code is 123456',
|
||||
received_at: '2026-04-14T10:00:00Z',
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(tokenCode.verification_code, '123456');
|
||||
assert.equal(tokenCode.mail.message_id, 'mail-1');
|
||||
assert.equal(tokenCode.mail.verification_code, '123456');
|
||||
|
||||
const normalizedMail = normalizeLuckmailTokenMail({
|
||||
message_id: 'mail-2',
|
||||
from: 'noreply@openai.com',
|
||||
subject: 'OpenAI security message',
|
||||
body: 'Your verification code is 654321.',
|
||||
received_at: '2026-04-14T10:01:00Z',
|
||||
});
|
||||
|
||||
assert.equal(normalizedMail.verification_code, '654321');
|
||||
assert.equal(extractLuckmailVerificationCode('你的验证码为 778899'), '778899');
|
||||
});
|
||||
|
||||
test('normalizeLuckmailProjectName and isLuckmailPurchaseForProject match openai case-insensitively', () => {
|
||||
assert.equal(normalizeLuckmailProjectName(' OpenAi '), 'openai');
|
||||
assert.equal(isLuckmailPurchaseForProject({
|
||||
id: 1,
|
||||
email_address: 'demo@outlook.com',
|
||||
token: 'tok-1',
|
||||
project_name: 'OpenAi',
|
||||
}, 'openai'), true);
|
||||
assert.equal(isLuckmailPurchaseForProject({
|
||||
id: 2,
|
||||
email_address: 'other@example.com',
|
||||
token: 'tok-2',
|
||||
project: 'OtherProject',
|
||||
}, 'openai'), false);
|
||||
});
|
||||
|
||||
test('normalizeLuckmailPurchaseListPage and normalizeLuckmailTags normalize list payloads', () => {
|
||||
const page = normalizeLuckmailPurchaseListPage({
|
||||
list: [{
|
||||
id: 3,
|
||||
email_address: 'demo@outlook.com',
|
||||
token: 'tok-3',
|
||||
project_name: 'OpenAi',
|
||||
}],
|
||||
total: 4,
|
||||
page: 2,
|
||||
page_size: 1,
|
||||
});
|
||||
assert.equal(page.total, 4);
|
||||
assert.equal(page.page, 2);
|
||||
assert.equal(page.page_size, 1);
|
||||
assert.equal(page.list[0].project_code, 'openai');
|
||||
|
||||
const tags = normalizeLuckmailTags([{
|
||||
id: 9,
|
||||
name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
limit_type: 0,
|
||||
}]);
|
||||
assert.deepEqual(tags[0], {
|
||||
id: 9,
|
||||
name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
remark: '',
|
||||
limit_type: 0,
|
||||
purchase_count: 0,
|
||||
created_at: null,
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizeLuckmailUsedPurchases keeps positive numeric keys only', () => {
|
||||
assert.deepEqual(normalizeLuckmailUsedPurchases({
|
||||
1: true,
|
||||
foo: true,
|
||||
'-2': true,
|
||||
3: false,
|
||||
}), {
|
||||
1: true,
|
||||
3: false,
|
||||
});
|
||||
});
|
||||
|
||||
test('pickReusableLuckmailPurchase only returns reusable openai purchase', () => {
|
||||
const purchases = [{
|
||||
id: 10,
|
||||
email_address: 'used@outlook.com',
|
||||
token: 'tok-used',
|
||||
project_name: 'openai',
|
||||
}, {
|
||||
id: 11,
|
||||
email_address: 'preserved@outlook.com',
|
||||
token: 'tok-preserved',
|
||||
project_name: 'OpenAi',
|
||||
tag_name: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
}, {
|
||||
id: 12,
|
||||
email_address: 'disabled@outlook.com',
|
||||
token: 'tok-disabled',
|
||||
project_name: 'openai',
|
||||
user_disabled: 1,
|
||||
}, {
|
||||
id: 13,
|
||||
email_address: 'expired@outlook.com',
|
||||
token: 'tok-expired',
|
||||
project_name: 'openai',
|
||||
warranty_until: '2026-04-14T09:00:00Z',
|
||||
}, {
|
||||
id: 14,
|
||||
email_address: 'other@example.com',
|
||||
token: 'tok-other',
|
||||
project_name: 'other',
|
||||
}, {
|
||||
id: 15,
|
||||
email_address: 'ready@outlook.com',
|
||||
token: 'tok-ready',
|
||||
project_name: 'OpenAi',
|
||||
warranty_until: '2026-04-15T09:00:00Z',
|
||||
}];
|
||||
|
||||
const reusable = filterReusableLuckmailPurchases(purchases, {
|
||||
projectCode: 'openai',
|
||||
usedPurchases: { 10: true },
|
||||
preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
now: Date.parse('2026-04-14T10:00:00Z'),
|
||||
});
|
||||
|
||||
assert.deepEqual(reusable.map((purchase) => purchase.id), [15]);
|
||||
assert.equal(pickReusableLuckmailPurchase(purchases, {
|
||||
projectCode: 'openai',
|
||||
usedPurchases: { 10: true },
|
||||
preserveTagName: DEFAULT_LUCKMAIL_PRESERVE_TAG_NAME,
|
||||
now: Date.parse('2026-04-14T10:00:00Z'),
|
||||
}).id, 15);
|
||||
});
|
||||
|
||||
test('pickLuckmailVerificationMail respects sender filters, time filters, and excluded codes', () => {
|
||||
const match = pickLuckmailVerificationMail([
|
||||
{
|
||||
message_id: 'old-mail',
|
||||
from: 'noreply@openai.com',
|
||||
subject: 'Your code is 111111',
|
||||
received_at: '2026-04-14T09:59:00Z',
|
||||
},
|
||||
{
|
||||
message_id: 'new-mail',
|
||||
from: 'noreply@openai.com',
|
||||
subject: 'Your code is 222222',
|
||||
received_at: '2026-04-14T10:05:00Z',
|
||||
},
|
||||
], {
|
||||
senderFilters: ['openai'],
|
||||
subjectFilters: ['code'],
|
||||
excludeCodes: ['111111'],
|
||||
afterTimestamp: Date.parse('2026-04-14T10:00:00Z'),
|
||||
});
|
||||
|
||||
assert.equal(match.code, '222222');
|
||||
assert.equal(match.mail.message_id, 'new-mail');
|
||||
});
|
||||
|
||||
test('isLuckmailMailNewerThanCursor compares message id and timestamp safely', () => {
|
||||
const cursor = buildLuckmailMailCursor({
|
||||
message_id: 'mail-1',
|
||||
received_at: '2026-04-14T10:00:00Z',
|
||||
});
|
||||
|
||||
assert.equal(isLuckmailMailNewerThanCursor({
|
||||
message_id: 'mail-1',
|
||||
received_at: '2026-04-14T10:00:00Z',
|
||||
}, cursor), false);
|
||||
|
||||
assert.equal(isLuckmailMailNewerThanCursor({
|
||||
message_id: 'mail-2',
|
||||
received_at: '2026-04-14T10:01:00Z',
|
||||
}, cursor), true);
|
||||
});
|
||||
|
||||
test('normalizeLuckmailMailCursor tolerates null cursor input', () => {
|
||||
const { normalizeLuckmailMailCursor } = require('../luckmail-utils.js');
|
||||
assert.deepEqual(normalizeLuckmailMailCursor(null), {
|
||||
messageId: '',
|
||||
receivedAt: '',
|
||||
});
|
||||
});
|
||||
|
||||
test('normalizeTimestamp treats LuckMail naive datetime strings as UTC', () => {
|
||||
assert.equal(
|
||||
normalizeTimestamp('2026-04-14 13:32:05'),
|
||||
Date.UTC(2026, 3, 14, 13, 32, 5, 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('buildLuckmailBaselineCursor tracks newest existing mail as baseline', () => {
|
||||
const cursor = buildLuckmailBaselineCursor([
|
||||
{
|
||||
message_id: 'mail-old',
|
||||
received_at: '2026-04-14 13:31:15',
|
||||
subject: '你的 ChatGPT 代码为 111111',
|
||||
},
|
||||
{
|
||||
message_id: 'mail-new',
|
||||
received_at: '2026-04-14 13:32:05',
|
||||
subject: '你的 ChatGPT 代码为 222222',
|
||||
},
|
||||
]);
|
||||
|
||||
assert.deepEqual(cursor, {
|
||||
messageId: 'mail-new',
|
||||
receivedAt: '2026-04-14 13:32:05',
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user